@my-swu/simulator-client 0.1.2 → 0.1.4

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.
Files changed (75) hide show
  1. package/README.md +1 -1
  2. package/dist/connection.js +2 -3
  3. package/dist/generated/index.d.ts +10 -0
  4. package/dist/generated/schemas.d.ts +1 -1
  5. package/dist/generated/schemas.js +466 -59
  6. package/dist/generated/types/admin-match-debug-bundle.d.ts +1 -0
  7. package/dist/generated/types/admin-match-debug-bundle.js +2 -0
  8. package/dist/generated/types/admin-match-list-response.d.ts +1 -0
  9. package/dist/generated/types/admin-match-list-response.js +2 -0
  10. package/dist/generated/types/admin-match-replay-response.d.ts +1 -0
  11. package/dist/generated/types/admin-match-replay-response.js +2 -0
  12. package/dist/generated/types/common.d.ts +143 -15
  13. package/dist/generated/types/create-match-history-replay-share-request.d.ts +1 -0
  14. package/dist/generated/types/create-match-history-replay-share-request.js +2 -0
  15. package/dist/generated/types/create-match-history-replay-share-response.d.ts +1 -0
  16. package/dist/generated/types/create-match-history-replay-share-response.js +2 -0
  17. package/dist/generated/types/inactive-match-recovery-request.d.ts +1 -0
  18. package/dist/generated/types/inactive-match-recovery-request.js +2 -0
  19. package/dist/generated/types/inactive-match-recovery-response.d.ts +1 -0
  20. package/dist/generated/types/inactive-match-recovery-response.js +2 -0
  21. package/dist/generated/types/match-access-recovery-request.d.ts +1 -0
  22. package/dist/generated/types/match-access-recovery-request.js +2 -0
  23. package/dist/generated/types/match-access-recovery-response.d.ts +1 -0
  24. package/dist/generated/types/match-access-recovery-response.js +2 -0
  25. package/dist/generated/types/match-recovery-request.d.ts +1 -0
  26. package/dist/generated/types/match-recovery-request.js +2 -0
  27. package/dist/generated/types/match-recovery-response.d.ts +1 -0
  28. package/dist/generated/types/match-recovery-response.js +2 -0
  29. package/dist/generated/types/shared-match-history-replay-response.d.ts +1 -0
  30. package/dist/generated/types/shared-match-history-replay-response.js +2 -0
  31. package/dist/generated/version.d.ts +1 -1
  32. package/dist/generated/version.js +1 -1
  33. package/dist/index.d.ts +3 -3
  34. package/dist/internal/match-history.d.ts +5 -0
  35. package/dist/internal/match-history.js +8 -0
  36. package/dist/local/engine.d.ts +2 -0
  37. package/dist/local/server/http-router.js +10 -0
  38. package/dist/local/wasm-engine.d.ts +1 -0
  39. package/dist/local/wasm-engine.js +1 -0
  40. package/dist/match-session/controller.d.ts +2 -0
  41. package/dist/match-session/controller.js +59 -17
  42. package/dist/match-session/methods.d.ts +8 -0
  43. package/dist/match-session/session-state.d.ts +5 -1
  44. package/dist/match-session/types.d.ts +3 -1
  45. package/dist/match-session.d.ts +1 -1
  46. package/dist/resources/admin.d.ts +14 -1
  47. package/dist/resources/admin.js +21 -0
  48. package/dist/resources/lobby-types.d.ts +4 -4
  49. package/dist/resources/match-history.d.ts +14 -1
  50. package/dist/resources/match-history.js +16 -0
  51. package/dist/resources/match-types.d.ts +11 -2
  52. package/dist/resources/matches.js +10 -0
  53. package/dist/validators.d.ts +11 -1
  54. package/package.json +1 -1
  55. package/schema/add-lobby-ai-player-request.schema.json +7 -0
  56. package/schema/admin-match-debug-bundle.schema.json +48 -0
  57. package/schema/admin-match-list-response.schema.json +23 -0
  58. package/schema/admin-match-replay-response.schema.json +14 -0
  59. package/schema/common.schema.json +176 -27
  60. package/schema/create-limited-event-request.schema.json +2 -4
  61. package/schema/create-lobby-request.schema.json +4 -8
  62. package/schema/create-match-history-replay-share-request.schema.json +22 -0
  63. package/schema/create-match-history-replay-share-response.schema.json +15 -0
  64. package/schema/create-match-request.schema.json +2 -3
  65. package/schema/create-tournament-room-request.schema.json +4 -8
  66. package/schema/limited-event-preflight-response.schema.json +1 -3
  67. package/schema/manifest.json +11 -1
  68. package/schema/match-access-recovery-request.schema.json +17 -0
  69. package/schema/match-access-recovery-response.schema.json +36 -0
  70. package/schema/match-access-response.schema.json +1 -3
  71. package/schema/match-history-entry.schema.json +1 -3
  72. package/schema/match-recovery-request.schema.json +33 -0
  73. package/schema/match-recovery-response.schema.json +36 -0
  74. package/schema/route-manifest.json +92 -0
  75. package/schema/shared-match-history-replay-response.schema.json +14 -0
@@ -2,6 +2,7 @@ import { MatchConnection } from '../connection.js';
2
2
  import { TypedEmitter } from '../internal/emitter.js';
3
3
  import { formatErrorMessages } from '../internal/errors.js';
4
4
  import { DEFAULT_ERROR_HISTORY_LIMIT, DEFAULT_MATCH_HISTORY_LIMIT } from '../internal/limits.js';
5
+ import { upsertMatchHistoryEntry } from '../internal/match-history.js';
5
6
  import { nonNegativeNumber, normalizeReconnectBackoff, reconnectDelayMs, } from '../internal/reconnect-backoff.js';
6
7
  import { createMatchSessionHeartbeat } from './heartbeat.js';
7
8
  import { hasAccessSnapshot, matchReferenceFrom } from './access.js';
@@ -91,6 +92,9 @@ export class MatchSessionController {
91
92
  get reconnectState() {
92
93
  return this.#reconnectState;
93
94
  }
95
+ get snapshotRevision() {
96
+ return this.#connection?.snapshotRevision ?? 0;
97
+ }
94
98
  get snapshot() {
95
99
  return this.#snapshot;
96
100
  }
@@ -207,6 +211,21 @@ export class MatchSessionController {
207
211
  reconnect(options = {}) {
208
212
  return this.connect(undefined, options);
209
213
  }
214
+ recoverNow() {
215
+ if (this.#manualClose || !this.#autoReconnect.enabled || this.#access == null) {
216
+ return;
217
+ }
218
+ this.#reconnectGeneration += 1;
219
+ if (this.#reconnectTimer != null) {
220
+ clearTimeout(this.#reconnectTimer);
221
+ this.#reconnectTimer = undefined;
222
+ }
223
+ // Detach before close so this intentional foreground replacement cannot
224
+ // schedule a duplicate close-driven retry.
225
+ this.#closeActiveConnection();
226
+ this.#setConnectionState('closed');
227
+ void this.#runAutoReconnectAttempt(1, this.#reconnectGeneration);
228
+ }
210
229
  ready() {
211
230
  this.#activeConnection().ready();
212
231
  }
@@ -333,8 +352,7 @@ export class MatchSessionController {
333
352
  this.#emitChange();
334
353
  }
335
354
  #recordHistoryEntry(entry) {
336
- const withoutDuplicate = this.#history.filter(existing => existing.id !== entry.id);
337
- this.#history = [...withoutDuplicate, entry].slice(-DEFAULT_MATCH_HISTORY_LIMIT);
355
+ this.#history = upsertMatchHistoryEntry(this.#history, entry);
338
356
  this.#emitter.emit('historyEntry', entry);
339
357
  this.#emitChange();
340
358
  }
@@ -368,15 +386,18 @@ export class MatchSessionController {
368
386
  }
369
387
  #scheduleAutoReconnect(attempt, generation) {
370
388
  if (attempt > this.#autoReconnect.maxRetries) {
389
+ const error = {
390
+ code: 'sdk_session_error',
391
+ message: 'automatic reconnect exhausted',
392
+ };
371
393
  this.#setReconnectState({
372
- phase: 'exhausted',
394
+ phase: 'terminal',
373
395
  attempts: this.#autoReconnect.maxRetries,
374
396
  maxAttempts: this.#autoReconnect.maxRetries,
397
+ cause: 'retriesExhausted',
398
+ error,
375
399
  });
376
- this.#recordError({
377
- code: 'sdk_session_error',
378
- message: 'automatic reconnect exhausted',
379
- });
400
+ this.#recordError(error);
380
401
  return;
381
402
  }
382
403
  const delayMs = reconnectDelayMs(this.#autoReconnect, attempt);
@@ -418,17 +439,23 @@ export class MatchSessionController {
418
439
  if (generation !== this.#reconnectGeneration || this.#manualClose) {
419
440
  return;
420
441
  }
421
- if (attempt >= this.#autoReconnect.maxRetries) {
422
- this.#recordError(errorPayloadFromUnknown(error));
423
- this.#recordError({
424
- code: 'sdk_session_error',
425
- message: 'automatic reconnect exhausted',
426
- });
442
+ const payload = errorPayloadFromUnknown(error);
443
+ const cause = terminalCauseForError(payload);
444
+ if (cause != null || attempt >= this.#autoReconnect.maxRetries) {
445
+ const terminalError = cause == null
446
+ ? {
447
+ code: 'sdk_session_error',
448
+ message: 'automatic reconnect exhausted',
449
+ }
450
+ : payload;
427
451
  this.#setReconnectState({
428
- phase: 'exhausted',
452
+ phase: 'terminal',
429
453
  attempts: attempt,
430
454
  maxAttempts: this.#autoReconnect.maxRetries,
455
+ cause: cause ?? 'retriesExhausted',
456
+ error: terminalError,
431
457
  });
458
+ this.#recordError(terminalError);
432
459
  this.#setConnectionState('closed');
433
460
  return;
434
461
  }
@@ -556,9 +583,24 @@ function sameReconnectState(left, right) {
556
583
  return right.phase === 'connecting'
557
584
  && left.attempt === right.attempt
558
585
  && left.maxAttempts === right.maxAttempts;
559
- case 'exhausted':
560
- return right.phase === 'exhausted'
586
+ case 'terminal':
587
+ return right.phase === 'terminal'
561
588
  && left.attempts === right.attempts
562
- && left.maxAttempts === right.maxAttempts;
589
+ && left.maxAttempts === right.maxAttempts
590
+ && left.cause === right.cause
591
+ && left.error.code === right.error.code
592
+ && left.error.message === right.error.message;
593
+ }
594
+ }
595
+ /** Maps definitive server rejections to terminal causes without parsing text. */
596
+ function terminalCauseForError(error) {
597
+ switch (error.code) {
598
+ case 'match_not_found':
599
+ return 'matchNotFound';
600
+ case 'invalid_seat_token':
601
+ case 'invalid_spectator_token':
602
+ return 'invalidCredentials';
603
+ default:
604
+ return undefined;
563
605
  }
564
606
  }
@@ -159,6 +159,14 @@ export interface MatchSessionMethods {
159
159
  * @see https://simulator-sdk.my-swu.com/guide/reconnect
160
160
  */
161
161
  reconnect(options?: MatchSessionConnectOptions): Promise<MatchSession>;
162
+ /**
163
+ * Immediately starts session-owned automatic recovery using stored access.
164
+ *
165
+ * This is intended for foreground restoration: it preserves automatic
166
+ * backoff after a failed dial instead of turning recovery into a one-shot
167
+ * manual reconnect.
168
+ */
169
+ recoverNow(): void;
162
170
  /**
163
171
  * Sends one ready message for the current seat.
164
172
  *
@@ -132,10 +132,14 @@ export type MatchSessionReconnectState = {
132
132
  attempt: number;
133
133
  maxAttempts: number;
134
134
  } | {
135
- phase: 'exhausted';
135
+ phase: 'terminal';
136
136
  attempts: number;
137
137
  maxAttempts: number;
138
+ cause: MatchSessionTerminalCause;
139
+ error: ErrorPayload;
138
140
  };
141
+ /** Server or transport condition which stopped automatic reconnect retries. */
142
+ export type MatchSessionTerminalCause = 'matchNotFound' | 'invalidCredentials' | 'retriesExhausted';
139
143
  /**
140
144
  * Options for creating one SDK-owned match session.
141
145
  *
@@ -3,7 +3,7 @@ import type { MatchHistoryFeedEntry } from '../generated/types/server-message.js
3
3
  import type { ConnectionState } from '../session.js';
4
4
  import type { MatchSessionMethods } from './methods.js';
5
5
  import type { MatchSeat, MatchSessionReconnectState, MatchSessionState } from './session-state.js';
6
- export type { MatchSeat, MatchSessionAccess, MatchSessionAutoReconnectOptions, MatchSessionAutoReconnectPolicy, MatchSessionConnectOptions, MatchSessionEvents, MatchSessionHeartbeatOptions, MatchSessionHeartbeatPolicy, MatchSessionOptions, MatchSessionReconnectState, MatchSessionSnapshotPredicate, MatchSessionState, MatchSessionWaitOptions, } from './session-state.js';
6
+ export type { MatchSeat, MatchSessionAccess, MatchSessionAutoReconnectOptions, MatchSessionAutoReconnectPolicy, MatchSessionConnectOptions, MatchSessionEvents, MatchSessionHeartbeatOptions, MatchSessionHeartbeatPolicy, MatchSessionOptions, MatchSessionReconnectState, MatchSessionSnapshotPredicate, MatchSessionState, MatchSessionTerminalCause, MatchSessionWaitOptions, } from './session-state.js';
7
7
  /**
8
8
  * Main ergonomic SDK API for interacting with one live match.
9
9
  *
@@ -45,6 +45,8 @@ export interface MatchSession extends MatchSessionMethods {
45
45
  readonly matchId: string | undefined;
46
46
  /** Automatic reconnect progress after an abnormal socket close. */
47
47
  readonly reconnectState: MatchSessionReconnectState;
48
+ /** Monotonic revision of the latest authoritative match snapshot. */
49
+ readonly snapshotRevision: number;
48
50
  /** Latest authoritative private snapshot for this seat. */
49
51
  readonly snapshot: GameState | undefined;
50
52
  /** Seat controlled by this session when known. */
@@ -1,3 +1,3 @@
1
1
  export { MatchSessionController } from './match-session/controller.js';
2
2
  export type { MatchSessionControllerOptions } from './match-session/controller.js';
3
- export type { MatchSeat, MatchSession, MatchSessionAccess, MatchSessionAutoReconnectOptions, MatchSessionAutoReconnectPolicy, MatchSessionConnectOptions, MatchSessionEvents, MatchSessionHeartbeatOptions, MatchSessionHeartbeatPolicy, MatchSessionOptions, MatchSessionReconnectState, MatchSessionSnapshotPredicate, MatchSessionState, MatchSessionWaitOptions, } from './match-session/types.js';
3
+ export type { MatchSeat, MatchSession, MatchSessionAccess, MatchSessionAutoReconnectOptions, MatchSessionAutoReconnectPolicy, MatchSessionConnectOptions, MatchSessionEvents, MatchSessionHeartbeatOptions, MatchSessionHeartbeatPolicy, MatchSessionOptions, MatchSessionReconnectState, MatchSessionTerminalCause, MatchSessionSnapshotPredicate, MatchSessionState, MatchSessionWaitOptions, } from './match-session/types.js';
@@ -1,4 +1,4 @@
1
- import type { SimulatorReportListResponse, SimulatorReportResponse, CardCatalogRefreshResponse, CreateMetaWindowRequest, MatchChatLookupResponse, MetaWindowsResponse, RankedLeaderboardQuery, RankedLeaderboardResponse, RankedProfileRequest, RankedProfileResponse, SetRankedAdmissionRequest, SetSimulatorMaintenanceRequest, SimulatorAdminStatusResponse } from '../generated/index.js';
1
+ import type { AdminMatchDebugBundle, AdminMatchListResponse, AdminMatchReplayResponse, SimulatorReportListResponse, SimulatorReportResponse, CardCatalogRefreshResponse, CreateMetaWindowRequest, MatchChatLookupResponse, MetaWindowsResponse, RankedLeaderboardQuery, RankedLeaderboardResponse, RankedProfileRequest, RankedProfileResponse, SetRankedAdmissionRequest, SetSimulatorMaintenanceRequest, SimulatorAdminStatusResponse } from '../generated/index.js';
2
2
  import type { SimulatorRequestOptions } from '../http/options.js';
3
3
  import type { EngineHttpRequester } from '../http/request.js';
4
4
  /**
@@ -10,6 +10,13 @@ export interface SimulatorReportListOptions {
10
10
  cursor?: string;
11
11
  limit?: number;
12
12
  }
13
+ /** Filters and cursor pagination controls for durable simulator admin games. */
14
+ export interface AdminGameListOptions {
15
+ matchId?: string;
16
+ playerSearch?: string;
17
+ cursor?: string;
18
+ limit?: number;
19
+ }
13
20
  /**
14
21
  * Bearer-token credentials for simulator admin calls.
15
22
  */
@@ -23,6 +30,12 @@ export interface SimulatorAdminAuth {
23
30
  * Simulator administration API methods.
24
31
  */
25
32
  export interface AdminResource {
33
+ /** Lists durable saved and completed matches for simulator support work. */
34
+ listGames(auth: SimulatorAdminAuth, query?: AdminGameListOptions, options?: SimulatorRequestOptions): Promise<AdminMatchListResponse>;
35
+ /** Returns all durable diagnostics for one match with restore credentials removed. */
36
+ getGameDebugBundle(auth: SimulatorAdminAuth, historyId: string, options?: SimulatorRequestOptions): Promise<AdminMatchDebugBundle>;
37
+ /** Returns a completed replay archive for one match. */
38
+ getGameReplay(auth: SimulatorAdminAuth, historyId: string, options?: SimulatorRequestOptions): Promise<AdminMatchReplayResponse>;
26
39
  /** Lists safe metadata for retained simulator diagnostic reports. */
27
40
  listSimulatorReports(auth: SimulatorAdminAuth, query?: SimulatorReportListOptions, options?: SimulatorRequestOptions): Promise<SimulatorReportListResponse>;
28
41
  /** Returns one full sanitized simulator support bundle. */
@@ -4,6 +4,9 @@ import { jsonBody, jsonHeaders } from '../http/json.js';
4
4
  */
5
5
  export function createAdminResource(requester) {
6
6
  return {
7
+ listGames: (auth, query, options) => requester.request('admin-match-list-response', adminGamesPath(query ?? {}), { headers: authHeaders(auth) }, options),
8
+ getGameDebugBundle: (auth, historyId, options) => requester.request('admin-match-debug-bundle', `/api/admin/games/${encodeURIComponent(historyId)}/debug`, { headers: authHeaders(auth) }, options),
9
+ getGameReplay: (auth, historyId, options) => requester.request('admin-match-replay-response', `/api/admin/games/${encodeURIComponent(historyId)}/replay`, { headers: authHeaders(auth) }, options),
7
10
  listSimulatorReports: (auth, query, options) => requester.request('simulator-report-list-response', simulatorReportsPath(query ?? {}), { headers: authHeaders(auth) }, options),
8
11
  getSimulatorReport: (auth, reportId, options) => requester.request('simulator-report-response', `/api/admin/reports/${encodeURIComponent(reportId)}`, { headers: authHeaders(auth) }, options),
9
12
  refreshCardCatalog: (auth, options) => requester.request('card-catalog-refresh-response', '/api/admin/card-catalog/refresh', { headers: authHeaders(auth), method: 'POST' }, options),
@@ -34,6 +37,24 @@ export function createAdminResource(requester) {
34
37
  rankedLeaderboard: (auth, windowId, query, options) => requester.request('ranked-leaderboard-response', leaderboardPath(requester, windowId, query ?? {}), { headers: authHeaders(auth) }, options),
35
38
  };
36
39
  }
40
+ /** Builds an encoded durable-game query without exposing the route shape. */
41
+ function adminGamesPath(options) {
42
+ const parameters = new URLSearchParams();
43
+ if (options.matchId) {
44
+ parameters.set('matchId', options.matchId);
45
+ }
46
+ if (options.playerSearch) {
47
+ parameters.set('playerSearch', options.playerSearch);
48
+ }
49
+ if (options.cursor) {
50
+ parameters.set('cursor', options.cursor);
51
+ }
52
+ if (options.limit != null) {
53
+ parameters.set('limit', String(options.limit));
54
+ }
55
+ const suffix = parameters.size === 0 ? '' : `?${parameters.toString()}`;
56
+ return `/api/admin/games${suffix}`;
57
+ }
37
58
  /** Builds a report-list path without exposing pagination implementation. */
38
59
  function simulatorReportsPath(options) {
39
60
  const parameters = new URLSearchParams();
@@ -62,9 +62,9 @@ export type LobbyConfig = {
62
62
  export interface CreateLobbyRequest {
63
63
  config: LobbyConfig;
64
64
  /** Fixed limited pack/leader seed. */
65
- packSeed?: number | null;
65
+ packSeed?: string | null;
66
66
  /** Fixed match seed reused for limited duel matches. */
67
- matchSeed?: number | null;
67
+ matchSeed?: string | null;
68
68
  /** Whether the lobby should appear in public browsing surfaces. */
69
69
  visibility: LobbyVisibility;
70
70
  hostName: string;
@@ -183,9 +183,9 @@ export interface LobbySnapshot {
183
183
  visibility: LobbyVisibility;
184
184
  config: LobbyConfig;
185
185
  /** Fixed limited pack/leader seed when configured. */
186
- packSeed?: number | null;
186
+ packSeed?: string | null;
187
187
  /** Fixed match seed when configured. */
188
- matchSeed?: number | null;
188
+ matchSeed?: string | null;
189
189
  hostPlayerId: string;
190
190
  requiredPlayerCount: number;
191
191
  /** Absolute server deadline for the ready-lobby auto-start countdown. */
@@ -1,4 +1,4 @@
1
- import type { MatchHistoryDeckStatsRequest, MatchHistoryDeckStatsResponse, MatchHistoryListRequest, MatchHistoryListResponse, MatchHistoryReplayRequest, MatchHistoryReplayResponse, MatchHistoryRestoreRequest, RestoreHistoryResponse } from '../generated/index.js';
1
+ import type { CreateMatchHistoryReplayShareRequest, CreateMatchHistoryReplayShareResponse, MatchRecoveryRequest, MatchRecoveryResponse, MatchHistoryDeckStatsRequest, MatchHistoryDeckStatsResponse, MatchHistoryListRequest, MatchHistoryListResponse, MatchHistoryReplayRequest, MatchHistoryReplayResponse, MatchHistoryRestoreRequest, RestoreHistoryResponse, SharedMatchHistoryReplayResponse } from '../generated/index.js';
2
2
  import type { SimulatorRequestOptions } from '../http/options.js';
3
3
  import type { EngineHttpRequester } from '../http/request.js';
4
4
  /**
@@ -10,11 +10,18 @@ export interface MatchHistoryRestoreInput extends MatchHistoryRestoreRequest {
10
10
  */
11
11
  historyId: string;
12
12
  }
13
+ /** Recovery request for a source runtime that is no longer available. */
14
+ export type MatchRecoveryInput = MatchRecoveryRequest;
13
15
  /** Request used to load a completed server-side replay archive. */
14
16
  export interface MatchHistoryReplayInput extends MatchHistoryReplayRequest {
15
17
  /** Durable history row id returned by {@link MatchHistoryResource.list}. */
16
18
  historyId: string;
17
19
  }
20
+ /** Participant-authorized request that creates one scoped replay-share link. */
21
+ export interface CreateMatchHistoryReplayShareInput extends CreateMatchHistoryReplayShareRequest {
22
+ /** Durable history row id returned by {@link MatchHistoryResource.list}. */
23
+ historyId: string;
24
+ }
18
25
  /**
19
26
  * Server-owned simulator history APIs.
20
27
  */
@@ -29,10 +36,16 @@ export interface MatchHistoryResource {
29
36
  list(request: MatchHistoryListRequest, options?: SimulatorRequestOptions): Promise<MatchHistoryListResponse>;
30
37
  /** Loads public and post-game reveal-all frames for an authorized result. */
31
38
  replay(request: MatchHistoryReplayInput, options?: SimulatorRequestOptions): Promise<MatchHistoryReplayResponse>;
39
+ /** Creates or returns a stable opaque replay link for one disclosure scope. */
40
+ createReplayShare(request: CreateMatchHistoryReplayShareInput, options?: SimulatorRequestOptions): Promise<CreateMatchHistoryReplayShareResponse>;
41
+ /** Loads a replay through an opaque link without participant identities. */
42
+ loadReplayShare(shareToken: string, options?: SimulatorRequestOptions): Promise<SharedMatchHistoryReplayResponse>;
32
43
  /**
33
44
  * Restores one authorized row whose eligibility is `available`.
34
45
  */
35
46
  restore(request: MatchHistoryRestoreInput, options?: SimulatorRequestOptions): Promise<RestoreHistoryResponse>;
47
+ /** Restores an owned Solo-versus-AI source after its runtime is lost. */
48
+ recover(request: MatchRecoveryInput, options?: SimulatorRequestOptions): Promise<MatchRecoveryResponse>;
36
49
  }
37
50
  /**
38
51
  * Dependencies needed by the match-history resource.
@@ -22,6 +22,17 @@ export function createMatchHistoryResource(options) {
22
22
  headers: jsonHeaders(),
23
23
  method: 'POST',
24
24
  }, options),
25
+ createReplayShare: (request, options) => requester.request('create-match-history-replay-share-response', `/api/match-history/${request.historyId}/replay-shares`, {
26
+ body: jsonBody(requester.validate('create-match-history-replay-share-request', {
27
+ identities: request.identities,
28
+ includeHiddenInformation: request.includeHiddenInformation,
29
+ })),
30
+ headers: jsonHeaders(),
31
+ method: 'POST',
32
+ }, options),
33
+ loadReplayShare: (shareToken, options) => requester.request('shared-match-history-replay-response', `/api/match-history/replay-shares/${shareToken}`, {
34
+ method: 'GET',
35
+ }, options),
25
36
  restore: (request, options) => requester.request('restore-history-response', `/api/match-history/${request.historyId}/restore`, {
26
37
  body: jsonBody(requester.validate('match-history-restore-request', {
27
38
  identities: request.identities,
@@ -30,5 +41,10 @@ export function createMatchHistoryResource(options) {
30
41
  headers: jsonHeaders(),
31
42
  method: 'POST',
32
43
  }, options),
44
+ recover: (request, options) => requester.request('match-recovery-response', '/api/match-history/recover', {
45
+ body: jsonBody(requester.validate('match-recovery-request', request)),
46
+ headers: jsonHeaders(),
47
+ method: 'POST',
48
+ }, options),
33
49
  };
34
50
  }
@@ -1,4 +1,4 @@
1
- import type { AiStatusPayload, CreateMatchRequest, GameState, JoinMatchRequest, ListActiveAiMatchesRequest, ListActiveAiMatchesResponse, MatchAccessResponse, MatchSpectatorAccessResponse, MatchSavePayload, RematchMatchResponse, RestoreMatchSaveResponse, SpectateMatchRequest, SubmitMatchDeckRequest } from '../generated/index.js';
1
+ import type { AiStatusPayload, CreateMatchRequest, GameState, JoinMatchRequest, ListActiveAiMatchesRequest, ListActiveAiMatchesResponse, MatchAccessRecoveryRequest, MatchAccessRecoveryResponse, MatchAccessResponse, MatchSpectatorAccessResponse, MatchSavePayload, RematchMatchResponse, RestoreMatchSaveResponse, SpectateMatchRequest, SubmitMatchDeckRequest } from '../generated/index.js';
2
2
  import type { FormatEventCommand } from '../generated/types/format-event-request.js';
3
3
  import type { SimulatorRequestOptions } from '../http/options.js';
4
4
  /**
@@ -87,7 +87,7 @@ export interface MatchAccessInput extends MatchReference {
87
87
  /**
88
88
  * Resolved seed for a private solo-vs-AI match, retained across reconnects.
89
89
  */
90
- replaySeed?: number;
90
+ replaySeed?: string;
91
91
  }
92
92
  export interface MatchSpectatorAccessInput extends MatchReference {
93
93
  /**
@@ -134,6 +134,15 @@ export type MatchConnectionAccessInput = MatchAccessInput | MatchSpectatorAccess
134
134
  * @see https://simulator-sdk.my-swu.com/guide/match-session
135
135
  */
136
136
  export interface MatchesResource {
137
+ /** Refreshes private seat access after validating its existing seat token. */
138
+ refreshAccess(access: MatchAccessInput, options?: SimulatorRequestOptions): Promise<MatchAccess>;
139
+ /**
140
+ * Recovers current host access for an owned remote Solo-versus-AI match.
141
+ *
142
+ * `unavailable` intentionally hides whether the match is absent, no longer
143
+ * resumable, or not owned by the supplied history identities.
144
+ */
145
+ recoverAccess(match: MatchReference, request: MatchAccessRecoveryRequest, options?: SimulatorRequestOptions): Promise<MatchAccessRecoveryResponse>;
137
146
  /** Records an intentional player departure without conceding the match. */
138
147
  leave(access: MatchAccessInput, options?: SimulatorRequestOptions): Promise<void>;
139
148
  /**
@@ -5,6 +5,16 @@ import { jsonBody, jsonHeaders } from '../http/json.js';
5
5
  export function createMatchesResource(options) {
6
6
  const { requester } = options;
7
7
  return {
8
+ refreshAccess: (access, requestOptions) => requester.request('match-access-response', `/api/matches/${matchIdFrom(access)}/access`, {
9
+ body: jsonBody(requester.validate('save-match-request', { seatToken: access.seatToken })),
10
+ headers: jsonHeaders(),
11
+ method: 'POST',
12
+ }, requestOptions),
13
+ recoverAccess: (match, request, options) => requester.request('match-access-recovery-response', `/api/matches/${matchIdFrom(match)}/recover-access`, {
14
+ body: jsonBody(requester.validate('match-access-recovery-request', request)),
15
+ headers: jsonHeaders(),
16
+ method: 'POST',
17
+ }, options),
8
18
  leave: (access, options) => requester.request('leave-match-response', `/api/matches/${matchIdFrom(access)}/leave`, {
9
19
  body: jsonBody(requester.validate('leave-match-request', { seatToken: access.seatToken })),
10
20
  headers: jsonHeaders(),
@@ -1,4 +1,4 @@
1
- import type { SimulatorReportListResponse, SimulatorReportResponse, CloseTournamentRoomRequest, AbandonMatchRequest, AbandonMatchResponse, AddLobbyAiPlayerRequest, ApiErrorResponse, CardCatalogRefreshResponse, CardsResponse, ClientMessage, CreateLimitedEventRequest, CreateLobbyRequest, CreateMetaWindowRequest, CreateMatchRequest, CreateTournamentRoomRequest, CreateTournamentTableRequest, ErrorPayload, FormatEventRequest, FormatEventResponse, GameCommand, GameEvent, GameState, GetMatchResponse, HealthResponse, JoinLimitedEventRequest, JoinLobbyRequest, JoinMatchRequest, JoinTournamentRoomRequest, JoinTournamentTableRequest, KickLobbyPlayerRequest, KickLobbySpectatorRequest, LeaveLobbyRequest, LeaveMatchRequest, LeaveMatchResponse, LeaveTournamentRoomRequest, LimitedEventAccessResponse, LimitedEventCommandRequest, LimitedEventPreflightResponse, LimitedEventResponse, LobbyAccessResponse, LobbyChatRequest, LobbyChatResponse, LobbyListResponse, LobbyListServerMessage, LobbyMatchmakingDecisionRequest, PrepareLobbyLimitedAiDecksRequest, LobbyResponse, LobbyServerMessage, LobbySyncRequest, ListActiveAiMatchesRequest, ListActiveAiMatchesResponse, MatchAccessResponse, MatchChatLookupResponse, MatchConnectionRole, MatchHistoryDeckStatsRequest, MatchHistoryDeckStatsResponse, MatchHistoryEntry, MatchHistoryListRequest, MatchHistoryListResponse, MatchHistoryReplayRequest, MatchHistoryReplayResponse, MatchHistoryRestoreRequest, RestoreHistoryResponse, MatchSavePayload, MatchSpectatorAccessResponse, MetaWindow, MetaWindowsResponse, LobbyMatchmakingRequest, ResolveCardIdsRequest, ResolveCardIdsResponse, RankedAdminStatus, RankedLeaderboardEntry, RankedLeaderboardQuery, RankedLeaderboardResponse, RankedMatchmakingView, RankedProfileRequest, RankedProfileResponse, RankedQueue, RematchMatchRequest, RematchMatchResponse, SaveMatchRequest, ReturnLobbyMatchRequest, ReturnTournamentMatchRequest, RestoreMatchSaveRequest, RestoreMatchSaveResponse, ServerMessage, AiStatusPayload, SetRankedAdmissionRequest, SetSimulatorMaintenanceRequest, SetAiPausedRequest, SimulatorAdminStatusResponse, SimulatorAvailabilityResponse, SpectateLobbyRequest, SpectateMatchRequest, StartLobbyLimitedMatchRequest, StartLobbyRequest, StartTournamentEventRequest, SubmitLobbyLimitedDeckRequest, SubmitMatchDeckRequest, SubmitMatchDeckResponse, SubmitTournamentDeckRequest, TournamentEventCommandRequest, TournamentRoomAccessResponse, TournamentRoomChatRequest, TournamentRoomChatResponse, TournamentRoomListResponse, TournamentRoomResponse, TournamentRoomServerMessage, TournamentRoomSyncRequest, UndoMatchRequest, UndoMatchResponse, UndoRespondMatchRequest, UpdateLobbyAiPlayerRequest, UpdateLobbyPlayerRequest, UpdateLobbySettingsRequest, UpdateTournamentRoomPlayerRequest, UpdateTournamentRoomSettingsRequest, WelcomePayload } from './generated/index.js';
1
+ import type { AdminMatchDebugBundle, AdminMatchListResponse, AdminMatchReplayResponse, SimulatorReportListResponse, SimulatorReportResponse, CloseTournamentRoomRequest, AbandonMatchRequest, AbandonMatchResponse, AddLobbyAiPlayerRequest, ApiErrorResponse, CardCatalogRefreshResponse, CardsResponse, ClientMessage, CreateMatchHistoryReplayShareRequest, CreateMatchHistoryReplayShareResponse, CreateLimitedEventRequest, CreateLobbyRequest, CreateMetaWindowRequest, CreateMatchRequest, CreateTournamentRoomRequest, CreateTournamentTableRequest, ErrorPayload, FormatEventRequest, FormatEventResponse, GameCommand, GameEvent, GameState, GetMatchResponse, HealthResponse, JoinLimitedEventRequest, JoinLobbyRequest, JoinMatchRequest, JoinTournamentRoomRequest, JoinTournamentTableRequest, KickLobbyPlayerRequest, KickLobbySpectatorRequest, LeaveLobbyRequest, LeaveMatchRequest, LeaveMatchResponse, LeaveTournamentRoomRequest, LimitedEventAccessResponse, LimitedEventCommandRequest, LimitedEventPreflightResponse, LimitedEventResponse, LobbyAccessResponse, LobbyChatRequest, LobbyChatResponse, LobbyListResponse, LobbyListServerMessage, LobbyMatchmakingDecisionRequest, PrepareLobbyLimitedAiDecksRequest, LobbyResponse, LobbyServerMessage, LobbySyncRequest, ListActiveAiMatchesRequest, ListActiveAiMatchesResponse, MatchAccessRecoveryRequest, MatchAccessRecoveryResponse, MatchAccessResponse, MatchChatLookupResponse, MatchConnectionRole, MatchHistoryDeckStatsRequest, MatchHistoryDeckStatsResponse, MatchHistoryEntry, MatchHistoryListRequest, MatchHistoryListResponse, MatchHistoryReplayRequest, MatchHistoryReplayResponse, MatchHistoryRestoreRequest, MatchRecoveryRequest, MatchRecoveryResponse, RestoreHistoryResponse, MatchSavePayload, MatchSpectatorAccessResponse, MetaWindow, MetaWindowsResponse, LobbyMatchmakingRequest, ResolveCardIdsRequest, ResolveCardIdsResponse, RankedAdminStatus, RankedLeaderboardEntry, RankedLeaderboardQuery, RankedLeaderboardResponse, RankedMatchmakingView, RankedProfileRequest, RankedProfileResponse, RankedQueue, RematchMatchRequest, RematchMatchResponse, SaveMatchRequest, ReturnLobbyMatchRequest, ReturnTournamentMatchRequest, RestoreMatchSaveRequest, RestoreMatchSaveResponse, ServerMessage, AiStatusPayload, SetRankedAdmissionRequest, SetSimulatorMaintenanceRequest, SetAiPausedRequest, SharedMatchHistoryReplayResponse, SimulatorAdminStatusResponse, SimulatorAvailabilityResponse, SpectateLobbyRequest, SpectateMatchRequest, StartLobbyLimitedMatchRequest, StartLobbyRequest, StartTournamentEventRequest, SubmitLobbyLimitedDeckRequest, SubmitMatchDeckRequest, SubmitMatchDeckResponse, SubmitTournamentDeckRequest, TournamentEventCommandRequest, TournamentRoomAccessResponse, TournamentRoomChatRequest, TournamentRoomChatResponse, TournamentRoomListResponse, TournamentRoomResponse, TournamentRoomServerMessage, TournamentRoomSyncRequest, UndoMatchRequest, UndoMatchResponse, UndoRespondMatchRequest, UpdateLobbyAiPlayerRequest, UpdateLobbyPlayerRequest, UpdateLobbySettingsRequest, UpdateTournamentRoomPlayerRequest, UpdateTournamentRoomSettingsRequest, WelcomePayload } from './generated/index.js';
2
2
  import type { SchemaValidator } from './validators/validator.js';
3
3
  /**
4
4
  * Exact schema-to-type map generated from Rust public wire contracts.
@@ -9,6 +9,9 @@ import type { SchemaValidator } from './validators/validator.js';
9
9
  * @see https://simulator-sdk.my-swu.com/guide/versioning
10
10
  */
11
11
  export interface SchemaTypeMap {
12
+ 'admin-match-list-response': AdminMatchListResponse;
13
+ 'admin-match-debug-bundle': AdminMatchDebugBundle;
14
+ 'admin-match-replay-response': AdminMatchReplayResponse;
12
15
  'simulator-report-list-response': SimulatorReportListResponse;
13
16
  'simulator-report-response': SimulatorReportResponse;
14
17
  'close-tournament-room-request': CloseTournamentRoomRequest;
@@ -69,8 +72,15 @@ export interface SchemaTypeMap {
69
72
  'match-history-list-response': MatchHistoryListResponse;
70
73
  'match-history-replay-request': MatchHistoryReplayRequest;
71
74
  'match-history-replay-response': MatchHistoryReplayResponse;
75
+ 'create-match-history-replay-share-request': CreateMatchHistoryReplayShareRequest;
76
+ 'create-match-history-replay-share-response': CreateMatchHistoryReplayShareResponse;
77
+ 'shared-match-history-replay-response': SharedMatchHistoryReplayResponse;
72
78
  'match-history-restore-request': MatchHistoryRestoreRequest;
73
79
  'restore-history-response': RestoreHistoryResponse;
80
+ 'match-recovery-request': MatchRecoveryRequest;
81
+ 'match-recovery-response': MatchRecoveryResponse;
82
+ 'match-access-recovery-request': MatchAccessRecoveryRequest;
83
+ 'match-access-recovery-response': MatchAccessRecoveryResponse;
74
84
  'match-save-payload': MatchSavePayload;
75
85
  'save-match-request': SaveMatchRequest;
76
86
  'match-spectator-access-response': MatchSpectatorAccessResponse;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@my-swu/simulator-client",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Typed TypeScript client for the swu-simulator HTTP and WebSocket engine.",
5
5
  "keywords": [
6
6
  "swu",
@@ -3,6 +3,13 @@
3
3
  "title": "AddLobbyAiPlayerRequest",
4
4
  "type": "object",
5
5
  "properties": {
6
+ "accountToken": {
7
+ "description": "Signed app account proof required to create a server-controlled AI.",
8
+ "type": [
9
+ "string",
10
+ "null"
11
+ ]
12
+ },
6
13
  "deck": {
7
14
  "$ref": "https://simulator-sdk.my-swu.com/schema/common.json#/$defs/DeckInput"
8
15
  },
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "AdminMatchDebugBundle",
4
+ "description": "Complete durable diagnostics for one match, excluding restore credentials.",
5
+ "type": "object",
6
+ "properties": {
7
+ "exportVersion": {
8
+ "type": "integer",
9
+ "format": "uint8",
10
+ "maximum": 255,
11
+ "minimum": 0
12
+ },
13
+ "exportedAtMs": {
14
+ "type": "integer",
15
+ "format": "uint64",
16
+ "minimum": 0
17
+ },
18
+ "game": {
19
+ "$ref": "https://simulator-sdk.my-swu.com/schema/common.json#/$defs/AdminMatchSummary"
20
+ },
21
+ "replay": {
22
+ "anyOf": [
23
+ {
24
+ "$ref": "https://simulator-sdk.my-swu.com/schema/common.json#/$defs/MatchHistoryReplayArchive"
25
+ },
26
+ {
27
+ "type": "null"
28
+ }
29
+ ]
30
+ },
31
+ "reports": {
32
+ "type": "array",
33
+ "items": {
34
+ "$ref": "https://simulator-sdk.my-swu.com/schema/common.json#/$defs/SimulatorReportResponse"
35
+ }
36
+ },
37
+ "save": {
38
+ "$ref": "https://simulator-sdk.my-swu.com/schema/common.json#/$defs/MatchSavePayload"
39
+ }
40
+ },
41
+ "required": [
42
+ "exportVersion",
43
+ "exportedAtMs",
44
+ "game",
45
+ "save",
46
+ "reports"
47
+ ]
48
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "AdminMatchListResponse",
4
+ "description": "Cursor-paginated durable match list for simulator administrators.",
5
+ "type": "object",
6
+ "properties": {
7
+ "games": {
8
+ "type": "array",
9
+ "items": {
10
+ "$ref": "https://simulator-sdk.my-swu.com/schema/common.json#/$defs/AdminMatchSummary"
11
+ }
12
+ },
13
+ "nextCursor": {
14
+ "type": [
15
+ "string",
16
+ "null"
17
+ ]
18
+ }
19
+ },
20
+ "required": [
21
+ "games"
22
+ ]
23
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "AdminMatchReplayResponse",
4
+ "description": "Completed replay archive returned to an authenticated simulator administrator.",
5
+ "type": "object",
6
+ "properties": {
7
+ "archive": {
8
+ "$ref": "https://simulator-sdk.my-swu.com/schema/common.json#/$defs/MatchHistoryReplayArchive"
9
+ }
10
+ },
11
+ "required": [
12
+ "archive"
13
+ ]
14
+ }