@spacemolt/lib 13.3.0 → 14.0.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/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.590.0`). **296 commands across 19 tools.**
4
+ Every command in `@spacemolt/lib`, generated from the live server spec (`v0.593.0`). **296 commands across 19 tools.**
5
5
 
6
6
  ## How to call a command
7
7
 
package/dist/account.d.ts CHANGED
@@ -251,12 +251,6 @@ export declare class Account {
251
251
  * Multiple listeners may be registered; returns an unsubscribe function.
252
252
  */
253
253
  onStateChange(listener: (changed: StateSection[]) => void): () => void;
254
- /**
255
- * Fan an event out to every registered listener. A throwing listener is
256
- * warned and skipped — it must never break the caller (frame routing,
257
- * mutation correlation, the reconnect loop) or starve later listeners.
258
- */
259
- private notifyListeners;
260
254
  private emitStateChange;
261
255
  /** Log out, releasing the connection's authenticated session. */
262
256
  logout(): Promise<void>;
@@ -6,6 +6,13 @@
6
6
  * this (loosely-typed) core.
7
7
  */
8
8
  import type { RawFrame } from '../protocol.js';
9
+ /**
10
+ * Fan an event out to every registered listener. A throwing listener is warned
11
+ * and skipped — a listener is told what already happened, so it must never
12
+ * break the caller (frame routing, mutation correlation, a connect or reconnect
13
+ * in progress) or starve the listeners after it.
14
+ */
15
+ export declare function notifyListeners<A extends unknown[]>(listeners: ReadonlySet<(...args: A) => void>, label: string, ...args: A): void;
9
16
  type FrameHandler = (frame: RawFrame) => void;
10
17
  /**
11
18
  * An async iterator over events of one kind, with internal buffering so a slow
@@ -1,5 +1,5 @@
1
1
  /** The gameserver spec version this command surface was generated from. */
2
- export declare const GENERATED_SPEC_VERSION = "v0.590.0";
2
+ export declare const GENERATED_SPEC_VERSION = "v0.593.0";
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 {
@@ -443,7 +443,7 @@ export interface SpacemoltFactionAdminPostMissionParams {
443
443
  title: string;
444
444
  /** Optional: triggers like 'open_to_all' to allow non-members */
445
445
  triggers?: string[];
446
- /** Mission type (delivery, combat, exploration, etc.) */
446
+ /** Mission type (delivery, combat, exploration, bounty, etc.) */
447
447
  type: string;
448
448
  }
449
449
  export interface SpacemoltFactionAdminPromoteParams {
@@ -0,0 +1,11 @@
1
+ /** Runtime kind of a frame-payload field. */
2
+ export type FieldKind = 'string' | 'number' | 'boolean' | 'string[]' | 'number[]' | 'boolean[]';
3
+ /** A frame-payload field's runtime kind, and whether the server always sends it. */
4
+ export interface FrameField {
5
+ kind: FieldKind;
6
+ required: boolean;
7
+ }
8
+ /** Every field of the server's `WelcomePayload`, with its runtime kind and required-ness. */
9
+ export declare const WELCOME_PAYLOAD_FIELDS: Readonly<Record<string, FrameField>>;
10
+ /** Every field of the server's `RegisteredPayload`, with its runtime kind and required-ness. */
11
+ export declare const REGISTERED_PAYLOAD_FIELDS: Readonly<Record<string, FrameField>>;
@@ -678,15 +678,23 @@ export type AttackLogEntry = {
678
678
  emergency_cloak_strength?: number;
679
679
  final_damage: number;
680
680
  flat_reduction_pct?: number;
681
+ /**
682
+ * Ship-level hit chance in [0.05,0.95] that every weapon starts from (separation, skills, modules, stance, speed). Each entry in weapons[] carries its own hit_chance with that weapon's ammo and mine modifiers added.
683
+ */
681
684
  hit_chance: number;
682
- hit_roll: number;
685
+ /**
686
+ * True when at least one weapon hit. Per-weapon outcomes are in weapons[].
687
+ */
683
688
  hit_success: boolean;
684
689
  hull_damage: number;
685
690
  ignored_resistance_pct?: number;
691
+ /**
692
+ * Sum of the damage of the weapons that hit, after offensive skill and buff bonuses, before the target's stance and defenses. 0 when every weapon missed. Rows recorded before per-weapon rolls report their single-roll volley damage here.
693
+ */
694
+ landed_damage: number;
686
695
  lifesteal_pct?: number;
687
696
  mine_duration?: number;
688
697
  off_buff_pct?: number;
689
- pre_hit_damage: number;
690
698
  raw_damage: number;
691
699
  secondary_kind?: string;
692
700
  shield_damage: number;
@@ -861,7 +869,7 @@ export type BattleCombatState = {
861
869
  */
862
870
  disruption_ticks?: number;
863
871
  /**
864
- * Your ship speed after EM disruption penalties. Webifier penalties are reported separately and affect escape timing.
872
+ * Your derived combat speed after fitted modules, temporary buffs, pilot skills, EM disruption, and enemy webifiers. This value drives hit chance, maneuvering, escape, and boarding pursuit.
865
873
  */
866
874
  effective_speed: number;
867
875
  /**
@@ -909,11 +917,11 @@ export type BattleCombatState = {
909
917
  */
910
918
  warp_disrupted: boolean;
911
919
  /**
912
- * Combined escape-speed penalty from enemy webifiers as a percentage. Webifier penalties add and cap at 75%.
920
+ * Combined combat-speed penalty from enemy webifiers as a percentage. Webifier penalties add and cap at 75%.
913
921
  */
914
922
  web_strength_pct?: number;
915
923
  /**
916
- * An enemy stasis webifier is increasing the time you need to escape.
924
+ * An enemy stasis webifier is reducing your effective combat speed.
917
925
  */
918
926
  webbed: boolean;
919
927
  };
@@ -5009,6 +5017,14 @@ export type ItemEffect = {
5009
5017
  ammo?: AmmoStats;
5010
5018
  amount?: number;
5011
5019
  duration?: number;
5020
+ /**
5021
+ * Magnitude of the consumable's additional effect. Read together with secondary_stat.
5022
+ */
5023
+ secondary_amount?: number;
5024
+ /**
5025
+ * Stat modified by the consumable's additional effect. Omitted when the item has only one effect.
5026
+ */
5027
+ secondary_stat?: string;
5012
5028
  stat?: string;
5013
5029
  subtype?: string;
5014
5030
  type: string;
@@ -5590,6 +5606,10 @@ export type Module = {
5590
5606
  hull_bonus?: number;
5591
5607
  hull_penalty?: number;
5592
5608
  id: string;
5609
+ /**
5610
+ * Percentage points subtracted from each hostile ECM lock-break roll. Fitted bonuses stack up to the combat resistance cap.
5611
+ */
5612
+ jam_resistance?: number;
5593
5613
  jam_strength?: number;
5594
5614
  latch_resistance?: number;
5595
5615
  latch_strength?: number;
@@ -5615,7 +5635,14 @@ export type Module = {
5615
5635
  reactive_resistance?: number;
5616
5636
  refit_capability?: boolean;
5617
5637
  remote_medical_treatment?: boolean;
5638
+ /**
5639
+ * Hull points repaired per tick on the most-wounded eligible ally.
5640
+ */
5618
5641
  remote_repair_power?: number;
5642
+ /**
5643
+ * Maximum remote-repair range in same-side radial-band distance to an eligible ally.
5644
+ */
5645
+ remote_repair_range?: number;
5619
5646
  required_skills?: {
5620
5647
  [key: string]: number;
5621
5648
  };
@@ -5637,6 +5664,10 @@ export type Module = {
5637
5664
  speed_penalty?: number;
5638
5665
  survey_power?: number;
5639
5666
  targeting_reduction?: number;
5667
+ /**
5668
+ * Percentage points subtracted from hostile sensor-dampener reach reduction. Fitted bonuses stack up to the combat resistance cap.
5669
+ */
5670
+ targeting_resistance?: number;
5640
5671
  tow_speed_penalty?: number;
5641
5672
  tracking_bonus?: number;
5642
5673
  type: string;
@@ -6531,6 +6562,14 @@ export type ObjectiveInfo = {
6531
6562
  system_name?: string;
6532
6563
  target_base_id?: string;
6533
6564
  target_base_name?: string;
6565
+ /**
6566
+ * kill_player objectives only: the username of the pilot this bounty names
6567
+ */
6568
+ target_player?: string;
6569
+ /**
6570
+ * kill_player objectives only: the ID of the pilot this bounty names
6571
+ */
6572
+ target_player_id?: string;
6534
6573
  type: string;
6535
6574
  };
6536
6575
  export type ObjectiveProgressInfo = {
@@ -6554,6 +6593,14 @@ export type ObjectiveProgressInfo = {
6554
6593
  system_name?: string;
6555
6594
  target_base?: string;
6556
6595
  target_base_name?: string;
6596
+ /**
6597
+ * kill_player objectives only: the username of the pilot this bounty names
6598
+ */
6599
+ target_player?: string;
6600
+ /**
6601
+ * kill_player objectives only: the ID of the pilot this bounty names
6602
+ */
6603
+ target_player_id?: string;
6557
6604
  type: string;
6558
6605
  };
6559
6606
  export type OpenOrderSummary = {
@@ -8255,7 +8302,7 @@ export type ShipClass = {
8255
8302
  [key: string]: unknown;
8256
8303
  }>;
8257
8304
  /**
8258
- * Minimum reputation with the ship's empire to commission or buy this ship (0 = no requirement)
8305
+ * Minimum reputation with the ship's empire to commission or buy this ship (0 = no requirement). Not applied to a faction-funded build at the faction's own station when the faction holds a buy_ship_license for this design — the license authorizes that build instead
8259
8306
  */
8260
8307
  required_reputation?: number;
8261
8308
  /**
@@ -9655,6 +9702,16 @@ export type V2Personnel = {
9655
9702
  injured_marines: number;
9656
9703
  };
9657
9704
  export type V2Player = {
9705
+ /**
9706
+ * NPC arena challenge win counts, keyed by challenge_id. Omitted until the first NPC-trial win; a missing challenge key means zero wins.
9707
+ */
9708
+ arena_won?: {
9709
+ [key: string]: number;
9710
+ };
9711
+ /**
9712
+ * Combat XP earned from arena fights during the UTC date in day, keyed by skill ID in by_skill. Omitted until the first arena fight; a missing skill key means zero XP. The ledger resets when the UTC date changes.
9713
+ */
9714
+ arena_xp?: ArenaXpLedger;
9658
9715
  /**
9659
9716
  * Empire IDs this player holds citizenship in (omitted when empty). Independent of the immutable origin empire.
9660
9717
  */
@@ -10027,6 +10084,18 @@ export type WeaponFireDetail = {
10027
10084
  crit_roll: number;
10028
10085
  damage: number;
10029
10086
  damage_type: string;
10087
+ /**
10088
+ * This weapon's own hit chance in [0.05,0.95]: the entry's hit_chance plus this weapon's loaded ammo accuracy and mine guidance. Present on every weapon that fired; absent on rows recorded before per-weapon rolls.
10089
+ */
10090
+ hit_chance?: number;
10091
+ /**
10092
+ * This weapon's roll in [0,1). It hit when hit_roll < hit_chance. Present on every weapon that fired; absent on rows recorded before per-weapon rolls.
10093
+ */
10094
+ hit_roll?: number;
10095
+ /**
10096
+ * True when this weapon connected. Omitted when it missed, and on rows recorded before per-weapon rolls (use the entry's hit_success there).
10097
+ */
10098
+ hit_success?: boolean;
10030
10099
  instance_id: string;
10031
10100
  name: string;
10032
10101
  type_bonus_pct: number;
@@ -17412,9 +17481,12 @@ export type SpacemoltFactionAdminPostMissionData = {
17412
17481
  * Destination for a deliver_item or dock_at_base objective, as either the station's Base ID or station POI ID. Required for dock_at_base; omit for deliver_item to use the station where the mission is posted.
17413
17482
  */
17414
17483
  target_base_id?: string;
17484
+ /**
17485
+ * Bounty target for a kill_player objective: the pilot's username or player ID. Required for kill_player.
17486
+ */
17415
17487
  target_id?: string;
17416
17488
  /**
17417
- * Objective type (deliver_item, kill_player, visit_system, etc.)
17489
+ * Objective type (deliver_item, kill_pirate, kill_player, visit_system, dock_at_base)
17418
17490
  */
17419
17491
  type: string;
17420
17492
  }>;
@@ -17436,7 +17508,7 @@ export type SpacemoltFactionAdminPostMissionData = {
17436
17508
  */
17437
17509
  triggers?: Array<string>;
17438
17510
  /**
17439
- * Mission type (delivery, combat, exploration, etc.)
17511
+ * Mission type (delivery, combat, exploration, bounty, etc.)
17440
17512
  */
17441
17513
  type: string;
17442
17514
  };
package/dist/index.js CHANGED
@@ -1,3 +1,22 @@
1
+ // src/generated/frames.gen.ts
2
+ var WELCOME_PAYLOAD_FIELDS = {
3
+ current_tick: { kind: "number", required: true },
4
+ game_info: { kind: "string", required: true },
5
+ help_text: { kind: "string", required: true },
6
+ motd: { kind: "string", required: false },
7
+ release_date: { kind: "string", required: true },
8
+ release_notes: { kind: "string[]", required: true },
9
+ server_time: { kind: "number", required: true },
10
+ terms: { kind: "string", required: true },
11
+ tick_rate: { kind: "number", required: true },
12
+ version: { kind: "string", required: true },
13
+ website: { kind: "string", required: true }
14
+ };
15
+ var REGISTERED_PAYLOAD_FIELDS = {
16
+ password: { kind: "string", required: true },
17
+ player_id: { kind: "string", required: true }
18
+ };
19
+
1
20
  // src/validation.ts
2
21
  function isRecord(value) {
3
22
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -24,14 +43,28 @@ function isActionErrorFrame(frame) {
24
43
  function isErrorFrame(frame) {
25
44
  return frame.type === "error" && hasPayload(frame) && typeof frame.payload.code === "string" && typeof frame.payload.message === "string" && (frame.payload.details === undefined || isRecord(frame.payload.details)) && (frame.payload.pending_command === undefined || typeof frame.payload.pending_command === "string");
26
45
  }
46
+ function matchesKind(value, kind) {
47
+ const element = kind.endsWith("[]") ? kind.slice(0, -2) : undefined;
48
+ if (element)
49
+ return Array.isArray(value) && value.every((item) => typeof item === element);
50
+ return typeof value === kind;
51
+ }
52
+ function matchesFields(payload, fields) {
53
+ return Object.entries(fields).every(([field, { kind, required }]) => {
54
+ const value = payload[field];
55
+ if (value === undefined)
56
+ return !required;
57
+ return matchesKind(value, kind);
58
+ });
59
+ }
27
60
  function isWelcomeFrame(frame) {
28
- return frame.type === "welcome" && hasPayload(frame) && typeof frame.payload.version === "string" && typeof frame.payload.release_date === "string" && Array.isArray(frame.payload.release_notes) && frame.payload.release_notes.every((note) => typeof note === "string") && typeof frame.payload.tick_rate === "number" && typeof frame.payload.current_tick === "number" && typeof frame.payload.server_time === "number" && (frame.payload.motd === undefined || typeof frame.payload.motd === "string") && typeof frame.payload.game_info === "string" && typeof frame.payload.website === "string" && typeof frame.payload.help_text === "string" && typeof frame.payload.terms === "string";
61
+ return frame.type === "welcome" && hasPayload(frame) && matchesFields(frame.payload, WELCOME_PAYLOAD_FIELDS);
29
62
  }
30
63
  function isLoggedInFrame(frame) {
31
64
  return frame.type === "logged_in" && hasPayload(frame);
32
65
  }
33
66
  function isRegisteredFrame(frame) {
34
- return frame.type === "registered" && hasPayload(frame) && typeof frame.payload.password === "string" && typeof frame.payload.player_id === "string";
67
+ return frame.type === "registered" && hasPayload(frame) && matchesFields(frame.payload, REGISTERED_PAYLOAD_FIELDS);
35
68
  }
36
69
  var STATE_SECTIONS = [
37
70
  "player",
@@ -45,7 +78,7 @@ var STATE_SECTIONS = [
45
78
  "prize_recoveries"
46
79
  ];
47
80
  // src/generated/actions.gen.ts
48
- var GENERATED_SPEC_VERSION = "v0.590.0";
81
+ var GENERATED_SPEC_VERSION = "v0.593.0";
49
82
  var ACTIONS = {
50
83
  "spacemolt_arena/accept": {
51
84
  tool: "spacemolt_arena",
@@ -996,7 +1029,7 @@ var ACTIONS = {
996
1029
  { name: "rewards", type: "{ credits?: number; items?: Record<string, unknown>[] }", required: true, description: "Mission rewards (credits, items, reputation)" },
997
1030
  { name: "title", type: "string", required: true, description: "Mission title" },
998
1031
  { name: "triggers", type: "string[]", required: false, description: "Optional: triggers like 'open_to_all' to allow non-members" },
999
- { name: "type", type: "string", required: true, description: "Mission type (delivery, combat, exploration, etc.)" }
1032
+ { name: "type", type: "string", required: true, description: "Mission type (delivery, combat, exploration, bounty, etc.)" }
1000
1033
  ]
1001
1034
  },
1002
1035
  "spacemolt_faction_admin/promote": {
@@ -3629,6 +3662,16 @@ function retryAfterMsFromClose(err) {
3629
3662
  }
3630
3663
 
3631
3664
  // src/events/emitter.ts
3665
+ function notifyListeners(listeners, label, ...args) {
3666
+ for (const listener of listeners) {
3667
+ try {
3668
+ listener(...args);
3669
+ } catch (err) {
3670
+ console.warn(`[spacemolt] ${label} listener threw: ${err}`);
3671
+ }
3672
+ }
3673
+ }
3674
+
3632
3675
  class EventStream {
3633
3676
  onClose;
3634
3677
  buffer = [];
@@ -3923,14 +3966,17 @@ class StateCache {
3923
3966
  function extractPendingAck(structured) {
3924
3967
  if (!structured)
3925
3968
  return;
3926
- if (structured.pending === true) {
3927
- return { command: String(structured.command ?? ""), message: String(structured.message ?? "") };
3928
- }
3929
3969
  const details = structured.details;
3930
- if (isRecord(details) && details.pending === true) {
3931
- return { command: String(details.command ?? ""), message: String(details.message ?? "") };
3932
- }
3933
- return;
3970
+ const source = structured.pending === true ? structured : isRecord(details) && details.pending === true ? details : undefined;
3971
+ if (!source)
3972
+ return;
3973
+ return {
3974
+ pending: true,
3975
+ command: String(source.command ?? ""),
3976
+ message: String(source.message ?? ""),
3977
+ ...typeof source.auto_docked === "boolean" && { auto_docked: source.auto_docked },
3978
+ ...typeof source.auto_undocked === "boolean" && { auto_undocked: source.auto_undocked }
3979
+ };
3934
3980
  }
3935
3981
 
3936
3982
  class Correlator {
@@ -3964,14 +4010,14 @@ class Correlator {
3964
4010
  return this.rejectInvalidResponse(requestId, pending, frame.type);
3965
4011
  const payload = frame.payload;
3966
4012
  if (pending.kind === "query") {
3967
- this.settle(requestId);
4013
+ this.pending.delete(requestId);
3968
4014
  pending.resolve({ result: payload.result, structuredContent: payload.structuredContent });
3969
4015
  return true;
3970
4016
  }
3971
4017
  const structured = payload.structuredContent;
3972
4018
  const ack = extractPendingAck(structured);
3973
4019
  if (!ack) {
3974
- this.settle(requestId);
4020
+ this.pending.delete(requestId);
3975
4021
  pending.resolve({
3976
4022
  command: String(structured?.command ?? ""),
3977
4023
  tick: 0,
@@ -3989,7 +4035,7 @@ class Correlator {
3989
4035
  if (!isActionResultFrame(frame))
3990
4036
  return this.rejectInvalidResponse(requestId, pending, frame.type);
3991
4037
  const payload = frame.payload;
3992
- this.settle(requestId);
4038
+ this.pending.delete(requestId);
3993
4039
  pending.resolve({
3994
4040
  command: payload.command,
3995
4041
  tick: payload.tick,
@@ -4002,14 +4048,14 @@ class Correlator {
4002
4048
  case "action_error": {
4003
4049
  if (!isActionErrorFrame(frame))
4004
4050
  return this.rejectInvalidResponse(requestId, pending, frame.type);
4005
- this.settle(requestId);
4051
+ this.pending.delete(requestId);
4006
4052
  pending.reject(errorFromActionFrame(frame));
4007
4053
  return true;
4008
4054
  }
4009
4055
  case "error": {
4010
4056
  if (!isErrorFrame(frame))
4011
4057
  return this.rejectInvalidResponse(requestId, pending, frame.type);
4012
- this.settle(requestId);
4058
+ this.pending.delete(requestId);
4013
4059
  pending.reject(errorFromFrame(frame));
4014
4060
  return true;
4015
4061
  }
@@ -4022,46 +4068,15 @@ class Correlator {
4022
4068
  pending.reject(err);
4023
4069
  this.pending.clear();
4024
4070
  }
4025
- settle(requestId) {
4026
- this.pending.delete(requestId);
4027
- }
4028
4071
  rejectInvalidResponse(requestId, pending, frameType) {
4029
- this.settle(requestId);
4072
+ this.pending.delete(requestId);
4030
4073
  pending.reject(new SpacemoltError("invalid_response", `Malformed ${frameType} frame for request ${requestId}`));
4031
4074
  return true;
4032
4075
  }
4033
4076
  }
4034
4077
 
4035
4078
  // src/transport/socket.ts
4036
- class NativeWebSocketAdapter {
4037
- socket;
4038
- constructor(url) {
4039
- this.socket = new WebSocket(url);
4040
- }
4041
- send(data) {
4042
- this.socket.send(data);
4043
- }
4044
- close(code, reason) {
4045
- this.socket.close(code, reason);
4046
- }
4047
- addEventListener(...args) {
4048
- switch (args[0]) {
4049
- case "open":
4050
- this.socket.addEventListener("open", () => args[1]());
4051
- break;
4052
- case "message":
4053
- this.socket.addEventListener("message", (event) => args[1]({ data: event.data }));
4054
- break;
4055
- case "close":
4056
- this.socket.addEventListener("close", (event) => args[1]({ code: event.code, reason: event.reason }));
4057
- break;
4058
- case "error":
4059
- this.socket.addEventListener("error", (event) => args[1](event));
4060
- break;
4061
- }
4062
- }
4063
- }
4064
- var defaultFactory = (url) => new NativeWebSocketAdapter(url);
4079
+ var defaultFactory = (url) => new WebSocket(url);
4065
4080
 
4066
4081
  class Socket {
4067
4082
  opts;
@@ -4436,17 +4451,8 @@ class Account {
4436
4451
  this.stateListeners.delete(listener);
4437
4452
  };
4438
4453
  }
4439
- notifyListeners(listeners, label, ...args) {
4440
- for (const listener of listeners) {
4441
- try {
4442
- listener(...args);
4443
- } catch (err) {
4444
- console.warn(`[spacemolt] ${label} listener threw: ${err}`);
4445
- }
4446
- }
4447
- }
4448
4454
  emitStateChange(changed) {
4449
- this.notifyListeners(this.stateListeners, "onStateChange", changed);
4455
+ notifyListeners(this.stateListeners, "onStateChange", changed);
4450
4456
  }
4451
4457
  async logout() {
4452
4458
  await this.query("spacemolt_auth", "logout");
@@ -4870,7 +4876,7 @@ class Account {
4870
4876
  if (this.shouldReconnect(err)) {
4871
4877
  this.reconnectLoop(err);
4872
4878
  } else if (!this.userClosing) {
4873
- this.notifyListeners(this.disconnectedListeners, "onDisconnected", err);
4879
+ notifyListeners(this.disconnectedListeners, "onDisconnected", err);
4874
4880
  }
4875
4881
  }
4876
4882
  shouldReconnect(err) {
@@ -4889,7 +4895,7 @@ class Account {
4889
4895
  const cfg = this.reconnectConfig;
4890
4896
  const retryAfterMs = retryAfterMsFromClose(err);
4891
4897
  for (let attempt = 1;attempt <= cfg.maxRetries; attempt++) {
4892
- this.notifyListeners(this.reconnectingListeners, "onReconnecting", attempt);
4898
+ notifyListeners(this.reconnectingListeners, "onReconnecting", attempt);
4893
4899
  const backoff = attempt === 1 && retryAfterMs !== undefined ? retryAfterMs : Math.min(cfg.baseDelayMs * 2 ** (attempt - 1), cfg.maxDelayMs);
4894
4900
  await delay(backoff);
4895
4901
  if (this.userClosing) {
@@ -4899,12 +4905,12 @@ class Account {
4899
4905
  try {
4900
4906
  await this.reconnectOnce();
4901
4907
  this.reconnecting = false;
4902
- this.notifyListeners(this.reconnectedListeners, "onReconnected");
4908
+ notifyListeners(this.reconnectedListeners, "onReconnected");
4903
4909
  return;
4904
4910
  } catch {}
4905
4911
  }
4906
4912
  this.reconnecting = false;
4907
- this.notifyListeners(this.disconnectedListeners, "onDisconnected", err);
4913
+ notifyListeners(this.disconnectedListeners, "onDisconnected", err);
4908
4914
  }
4909
4915
  async reconnectOnce() {
4910
4916
  if (!this.credentialsProvider)
@@ -5188,16 +5194,13 @@ class SpacemoltClient {
5188
5194
  return () => this.accountDisconnectedListeners.delete(listener);
5189
5195
  }
5190
5196
  notifyAccountConnected(account) {
5191
- for (const listener of this.accountConnectedListeners)
5192
- listener(account);
5197
+ notifyListeners(this.accountConnectedListeners, "onAccountConnected", account);
5193
5198
  }
5194
5199
  notifyAccountReconnected(account) {
5195
- for (const listener of this.accountReconnectedListeners)
5196
- listener(account);
5200
+ notifyListeners(this.accountReconnectedListeners, "onAccountReconnected", account);
5197
5201
  }
5198
5202
  notifyAccountDisconnected(id, err) {
5199
- for (const listener of this.accountDisconnectedListeners)
5200
- listener(id, err);
5203
+ notifyListeners(this.accountDisconnectedListeners, "onAccountDisconnected", id, err);
5201
5204
  }
5202
5205
  get httpBaseUrl() {
5203
5206
  return this.opts.httpBaseUrl ?? (this.opts.url ? httpBaseFromWs(this.opts.url) : DEFAULT_HTTP_BASE);
@@ -7,7 +7,7 @@
7
7
  * spec instead (see src/generated/). Endpoint: `/ws/v2`.
8
8
  */
9
9
  import type { NotificationPayloads, TypedNotificationType } from './generated/notifications.gen.js';
10
- import type { LoggedInPayload, RegisteredPayload, V2GameState, WelcomePayload } from './generated/openapi/types.gen.js';
10
+ import type { LoggedInPayload, NotificationActionError, NotificationActionResult, PendingActionResponse, RegisteredPayload, V2GameState, WelcomePayload } from './generated/openapi/types.gen.js';
11
11
  /** Inbound frame: client -> server. `payload` is omitted when an action takes none. */
12
12
  export interface InboundFrame {
13
13
  tool: string;
@@ -27,36 +27,25 @@ export interface ResultFrame {
27
27
  structuredContent?: Record<string, unknown>;
28
28
  };
29
29
  }
30
- /** Mutation-ack: a `result` frame whose structuredContent flags `pending: true`. */
31
- export interface PendingAck {
32
- pending: true;
33
- command: string;
34
- message: string;
35
- }
36
30
  /** Outcome push for a queued mutation; echoes the original request_id. */
37
31
  export interface ActionResultFrame {
38
32
  type: 'action_result';
39
33
  request_id?: string;
40
- payload: {
41
- command: string;
42
- tick: number;
34
+ /**
35
+ * The server's published `Notification_action_result`, except for `result`:
36
+ * the spec leaves it untyped because the v1 and v2 shapes differ, and on
37
+ * `/ws/v2` it is always the delta. Narrow it here rather than lose the type.
38
+ */
39
+ payload: Omit<NotificationActionResult, 'result'> & {
43
40
  /** A V2GameState delta — only the sections that changed. See StateDelta. */
44
41
  result: StateDelta;
45
- auto_docked?: boolean;
46
- auto_undocked?: boolean;
47
42
  };
48
43
  }
49
44
  /** Failure outcome for a queued mutation. */
50
45
  export interface ActionErrorFrame {
51
46
  type: 'action_error';
52
47
  request_id?: string;
53
- payload: {
54
- command: string;
55
- tick: number;
56
- code: string;
57
- message: string;
58
- details?: Record<string, unknown>;
59
- };
48
+ payload: NotificationActionError;
60
49
  }
61
50
  /** Generic error frame emitted by the framing layer or a handler. */
62
51
  export interface ErrorFrame {
@@ -163,11 +152,13 @@ export interface QueryResult<T = Record<string, unknown>> {
163
152
  /** Raw JSON for programmatic consumption, typed to the command's response. */
164
153
  structuredContent?: T;
165
154
  }
166
- /** The immediate `pending: true` acknowledgement for a queued mutation. */
167
- export interface MutationAck {
168
- command: string;
169
- message: string;
170
- }
155
+ /**
156
+ * The immediate `pending: true` acknowledgement for a queued mutation. The
157
+ * server's published `PendingActionResponse`, so the `auto_docked`/
158
+ * `auto_undocked` flags it carries reach the caller at ack time rather than
159
+ * only on the final outcome.
160
+ */
161
+ export type MutationAck = PendingActionResponse;
171
162
  /**
172
163
  * Resolved value of a two-phase mutation, delivered when the action executes.
173
164
  * `TDetails` is the action-specific shape of `delta.details` (e.g. `jump` ->
@@ -50,6 +50,5 @@ export declare class Correlator {
50
50
  handle(frame: RawFrame): boolean;
51
51
  /** Reject every in-flight request — used when the socket closes. */
52
52
  rejectAll(err: ConnectionClosedError | SpacemoltError): void;
53
- private settle;
54
53
  private rejectInvalidResponse;
55
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spacemolt/lib",
3
- "version": "13.3.0",
3
+ "version": "14.0.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",