@chaos-overlords/kernel 0.8.2 → 0.9.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/domain/entities.d.ts +4 -0
- package/dist/domain/entities.d.ts.map +1 -1
- package/dist/domain/entities.js +9 -1
- package/dist/domain/entities.js.map +1 -1
- package/dist/logic/canonical-json.js +33 -5
- package/dist/logic/canonical-json.js.map +1 -1
- package/dist/logic/turn-logic.d.ts.map +1 -1
- package/dist/logic/turn-logic.js +2 -4
- package/dist/logic/turn-logic.js.map +1 -1
- package/dist/services/LobbyService.d.ts +8 -0
- package/dist/services/LobbyService.d.ts.map +1 -1
- package/dist/services/LobbyService.js +49 -73
- package/dist/services/LobbyService.js.map +1 -1
- package/dist/services/MatchQueryService.d.ts +4 -2
- package/dist/services/MatchQueryService.d.ts.map +1 -1
- package/dist/services/MatchQueryService.js +14 -5
- package/dist/services/MatchQueryService.js.map +1 -1
- package/dist/services/RateLimiter.d.ts +30 -4
- package/dist/services/RateLimiter.d.ts.map +1 -1
- package/dist/services/RateLimiter.js +58 -23
- package/dist/services/RateLimiter.js.map +1 -1
- package/dist/services/RetentionService.d.ts +2 -0
- package/dist/services/RetentionService.d.ts.map +1 -1
- package/dist/services/RetentionService.js +14 -34
- package/dist/services/RetentionService.js.map +1 -1
- package/dist/services/SnapshotService.d.ts +9 -0
- package/dist/services/SnapshotService.d.ts.map +1 -1
- package/dist/services/SnapshotService.js +19 -23
- package/dist/services/SnapshotService.js.map +1 -1
- package/dist/services/TurnService.d.ts +7 -9
- package/dist/services/TurnService.d.ts.map +1 -1
- package/dist/services/TurnService.js +33 -47
- package/dist/services/TurnService.js.map +1 -1
- package/dist/services/guards.d.ts +9 -0
- package/dist/services/guards.d.ts.map +1 -0
- package/dist/services/guards.js +22 -0
- package/dist/services/guards.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MatchQueryService.js","sourceRoot":"","sources":["../../src/services/MatchQueryService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,
|
|
1
|
+
{"version":3,"file":"MatchQueryService.js","sourceRoot":"","sources":["../../src/services/MatchQueryService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { Clock } from '../ports/runtime';
|
|
2
|
+
export interface RateLimiterOptions {
|
|
3
|
+
limit: number;
|
|
4
|
+
windowMs: number;
|
|
5
|
+
/** Residency bound; defaults to {@link DEFAULT_MAX_KEYS}. */
|
|
6
|
+
maxKeys?: number;
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* Fixed-window counter per key, in memory. It protects the unauthenticated doors (create, join)
|
|
4
10
|
* from brute force on one process; a horizontally scaled deployment puts a shared limiter (a WAF
|
|
@@ -8,17 +14,37 @@ export declare class RateLimiter {
|
|
|
8
14
|
private readonly clock;
|
|
9
15
|
private readonly options;
|
|
10
16
|
private readonly windows;
|
|
17
|
+
private readonly maxKeys;
|
|
11
18
|
private lastPrune;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
19
|
+
private lastWall;
|
|
20
|
+
private elapsed;
|
|
21
|
+
constructor(clock: Clock, options: RateLimiterOptions);
|
|
16
22
|
/** Returns the retry delay in seconds when over the limit, or null when the call is allowed. */
|
|
17
23
|
take(key: string): number | null;
|
|
18
24
|
/** What `take` would answer right now, spending nothing. */
|
|
19
25
|
peek(key: string): number | null;
|
|
20
26
|
/** How much of `key`'s budget the current window has already spent; 0 once it has rolled. */
|
|
21
27
|
spent(key: string): number;
|
|
28
|
+
/**
|
|
29
|
+
* Milliseconds on a clock that only moves forward, accumulated from the wall clock's forward steps.
|
|
30
|
+
*
|
|
31
|
+
* Production passes the wall clock, which NTP can step backwards. Measured directly, a step back
|
|
32
|
+
* would give new windows an earlier start than older ones already at the head, breaking the
|
|
33
|
+
* eviction order, and make elapsed time negative, keeping spent windows alive past `windowMs`.
|
|
34
|
+
* A backward step here just pauses the limiter's time until the wall clock moves forward again.
|
|
35
|
+
*/
|
|
36
|
+
private tick;
|
|
37
|
+
/**
|
|
38
|
+
* The window `key` is spending from, or undefined once it has rolled or was never opened.
|
|
39
|
+
*
|
|
40
|
+
* A rolled window is removed here rather than overwritten later: Map#set keeps an existing key's
|
|
41
|
+
* insertion position, so overwriting would leave a fresh, possibly spent window at the head, first
|
|
42
|
+
* in line for eviction.
|
|
43
|
+
*/
|
|
44
|
+
private live;
|
|
45
|
+
private expired;
|
|
46
|
+
/** Whole seconds until `entry`'s window rolls, rounded up. */
|
|
47
|
+
private retryAfter;
|
|
22
48
|
/**
|
|
23
49
|
* Drop windows that have expired, at most once per window period.
|
|
24
50
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAe7C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAOD;;;;GAIG;AACH,qBAAa,WAAW;IAQpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,OAAO,CAAI;IAEnB,YACmB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,kBAAkB,EAG7C;IAED,gGAAgG;IAChG,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa/B;IAED,4DAA4D;IAC5D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK/B;IAED,6FAA6F;IAC7F,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzB;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,IAAI;IAOZ;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,OAAO;IAIf,8DAA8D;IAC9D,OAAO,CAAC,UAAU;IAIlB;;;;;;;OAOG;IACH,OAAO,CAAC,KAAK;IAQb,0FAA0F;IAC1F,OAAO,CAAC,WAAW;CAOpB"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* How many distinct keys one limiter holds before the oldest are dropped.
|
|
2
|
+
* How many distinct keys one limiter holds by default before the oldest are dropped.
|
|
3
3
|
*
|
|
4
4
|
* `prune` only collects windows that have EXPIRED, at most once per window period, which is right
|
|
5
5
|
* for a minute-long window and wrong for a day-long one: the bug-report budget's window is
|
|
6
6
|
* twenty-four hours, so every address that ever posted stayed resident for a day or two on an
|
|
7
|
-
* unauthenticated route.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* unauthenticated route. The Map iterates in window-start order — windows open on a clock that
|
|
8
|
+
* never runs backwards, and a rolled window is removed before it reopens at the tail — so the head
|
|
9
|
+
* is the oldest window and evicting from there is one shift. Dropping a live window forgives that
|
|
10
|
+
* key's remaining budget, which is the safe direction to fail on a memory bound.
|
|
10
11
|
*/
|
|
11
|
-
const
|
|
12
|
+
const DEFAULT_MAX_KEYS = 50_000;
|
|
12
13
|
/**
|
|
13
14
|
* Fixed-window counter per key, in memory. It protects the unauthenticated doors (create, join)
|
|
14
15
|
* from brute force on one process; a horizontally scaled deployment puts a shared limiter (a WAF
|
|
@@ -18,44 +19,78 @@ export class RateLimiter {
|
|
|
18
19
|
clock;
|
|
19
20
|
options;
|
|
20
21
|
windows = new Map();
|
|
22
|
+
maxKeys;
|
|
21
23
|
lastPrune = Number.NEGATIVE_INFINITY;
|
|
24
|
+
lastWall = Number.NaN;
|
|
25
|
+
elapsed = 0;
|
|
22
26
|
constructor(clock, options) {
|
|
23
27
|
this.clock = clock;
|
|
24
28
|
this.options = options;
|
|
29
|
+
this.maxKeys = options.maxKeys ?? DEFAULT_MAX_KEYS;
|
|
25
30
|
}
|
|
26
31
|
/** Returns the retry delay in seconds when over the limit, or null when the call is allowed. */
|
|
27
32
|
take(key) {
|
|
28
|
-
const now = this.
|
|
29
|
-
const entry = this.
|
|
30
|
-
if (!entry
|
|
33
|
+
const now = this.tick();
|
|
34
|
+
const entry = this.live(key, now);
|
|
35
|
+
if (!entry) {
|
|
36
|
+
// `live` removed any rolled window, so this appends at the tail and keeps the Map ordered.
|
|
31
37
|
this.windows.set(key, { windowStart: now, count: 1 });
|
|
32
38
|
this.prune(now);
|
|
33
39
|
this.evictOldest();
|
|
34
40
|
return null;
|
|
35
41
|
}
|
|
36
|
-
if (entry.count >= this.options.limit)
|
|
37
|
-
return
|
|
38
|
-
}
|
|
42
|
+
if (entry.count >= this.options.limit)
|
|
43
|
+
return this.retryAfter(entry, now);
|
|
39
44
|
entry.count += 1;
|
|
40
45
|
return null;
|
|
41
46
|
}
|
|
42
47
|
/** What `take` would answer right now, spending nothing. */
|
|
43
48
|
peek(key) {
|
|
44
|
-
const now = this.
|
|
45
|
-
const entry = this.
|
|
46
|
-
if (!entry ||
|
|
47
|
-
return null;
|
|
48
|
-
if (entry.count < this.options.limit)
|
|
49
|
+
const now = this.tick();
|
|
50
|
+
const entry = this.live(key, now);
|
|
51
|
+
if (!entry || entry.count < this.options.limit)
|
|
49
52
|
return null;
|
|
50
|
-
return
|
|
53
|
+
return this.retryAfter(entry, now);
|
|
51
54
|
}
|
|
52
55
|
/** How much of `key`'s budget the current window has already spent; 0 once it has rolled. */
|
|
53
56
|
spent(key) {
|
|
54
|
-
|
|
57
|
+
return this.live(key, this.tick())?.count ?? 0;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Milliseconds on a clock that only moves forward, accumulated from the wall clock's forward steps.
|
|
61
|
+
*
|
|
62
|
+
* Production passes the wall clock, which NTP can step backwards. Measured directly, a step back
|
|
63
|
+
* would give new windows an earlier start than older ones already at the head, breaking the
|
|
64
|
+
* eviction order, and make elapsed time negative, keeping spent windows alive past `windowMs`.
|
|
65
|
+
* A backward step here just pauses the limiter's time until the wall clock moves forward again.
|
|
66
|
+
*/
|
|
67
|
+
tick() {
|
|
68
|
+
const wall = this.clock.now().getTime();
|
|
69
|
+
if (wall > this.lastWall)
|
|
70
|
+
this.elapsed += wall - this.lastWall;
|
|
71
|
+
this.lastWall = wall;
|
|
72
|
+
return this.elapsed;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The window `key` is spending from, or undefined once it has rolled or was never opened.
|
|
76
|
+
*
|
|
77
|
+
* A rolled window is removed here rather than overwritten later: Map#set keeps an existing key's
|
|
78
|
+
* insertion position, so overwriting would leave a fresh, possibly spent window at the head, first
|
|
79
|
+
* in line for eviction.
|
|
80
|
+
*/
|
|
81
|
+
live(key, now) {
|
|
55
82
|
const entry = this.windows.get(key);
|
|
56
|
-
if (!entry ||
|
|
57
|
-
return
|
|
58
|
-
|
|
83
|
+
if (!entry || !this.expired(entry, now))
|
|
84
|
+
return entry;
|
|
85
|
+
this.windows.delete(key);
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
expired(entry, now) {
|
|
89
|
+
return now - entry.windowStart >= this.options.windowMs;
|
|
90
|
+
}
|
|
91
|
+
/** Whole seconds until `entry`'s window rolls, rounded up. */
|
|
92
|
+
retryAfter(entry, now) {
|
|
93
|
+
return Math.ceil((entry.windowStart + this.options.windowMs - now) / 1000);
|
|
59
94
|
}
|
|
60
95
|
/**
|
|
61
96
|
* Drop windows that have expired, at most once per window period.
|
|
@@ -70,13 +105,13 @@ export class RateLimiter {
|
|
|
70
105
|
return;
|
|
71
106
|
this.lastPrune = now;
|
|
72
107
|
for (const [key, entry] of this.windows) {
|
|
73
|
-
if (
|
|
108
|
+
if (this.expired(entry, now))
|
|
74
109
|
this.windows.delete(key);
|
|
75
110
|
}
|
|
76
111
|
}
|
|
77
112
|
/** Hard bound on residency, for windows too long for `prune` to be the only collector. */
|
|
78
113
|
evictOldest() {
|
|
79
|
-
while (this.windows.size >
|
|
114
|
+
while (this.windows.size > this.maxKeys) {
|
|
80
115
|
const oldest = this.windows.keys().next();
|
|
81
116
|
if (oldest.done)
|
|
82
117
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimiter.js","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"RateLimiter.js","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,5 +46,7 @@ export declare class RetentionService {
|
|
|
46
46
|
private collectAbandonedLive;
|
|
47
47
|
/** The same delete without the roster test, on a window long enough to stand in for it. */
|
|
48
48
|
private collectSilentLive;
|
|
49
|
+
/** One retention window: a window of 0 is switched off and reads neither the clock nor storage. */
|
|
50
|
+
private collectWindow;
|
|
49
51
|
}
|
|
50
52
|
//# sourceMappingURL=RetentionService.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RetentionService.d.ts","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAKxC,MAAM,WAAW,eAAe;IAC9B,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;;;OAQG;IACH,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;OASG;IACH,kBAAkB,EAAE,MAAM,CAAA;IAC1B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,eAAO,MAAM,iBAAiB,EAAE,eAK/B,CAAA;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFzB,YACmB,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,EACtD,MAAM,GAAE,eAAmC,EAC1D;IAEE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAM/B;
|
|
1
|
+
{"version":3,"file":"RetentionService.d.ts","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAKxC,MAAM,WAAW,eAAe;IAC9B,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;;;OAQG;IACH,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;OASG;IACH,kBAAkB,EAAE,MAAM,CAAA;IAC1B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,eAAO,MAAM,iBAAiB,EAAE,eAK/B,CAAA;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFzB,YACmB,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,EACtD,MAAM,GAAE,eAAmC,EAC1D;IAEE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAM/B;IAED,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,oBAAoB;IAS5B,2FAA2F;IAC3F,OAAO,CAAC,iBAAiB;IASzB,mGAAmG;YACrF,aAAa;CAW5B"}
|
|
@@ -29,44 +29,24 @@ export class RetentionService {
|
|
|
29
29
|
(await this.collectAbandonedLive()) +
|
|
30
30
|
(await this.collectSilentLive()));
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return 0;
|
|
35
|
-
const before = new Date(this.deps.clock.now().getTime() - this.policy.maxAgeMs);
|
|
36
|
-
const deleted = await this.deps.storage.matches.deleteInactive(COLLECTABLE, before, this.policy.batchSize);
|
|
37
|
-
if (deleted > 0) {
|
|
38
|
-
this.deps.logger.info('retention deleted inactive matches', {
|
|
39
|
-
deleted,
|
|
40
|
-
before: before.toISOString(),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
return deleted;
|
|
32
|
+
collectTerminated() {
|
|
33
|
+
return this.collectWindow(this.policy.maxAgeMs, 'retention deleted inactive matches', (before) => this.deps.storage.matches.deleteInactive(COLLECTABLE, before, this.policy.batchSize));
|
|
44
34
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return 0;
|
|
48
|
-
const before = new Date(this.deps.clock.now().getTime() - this.policy.abandonedLiveMaxAgeMs);
|
|
49
|
-
const deleted = await this.deps.storage.matches.deleteAbandonedLive(before, this.policy.batchSize, true);
|
|
50
|
-
if (deleted > 0) {
|
|
51
|
-
this.deps.logger.info('retention deleted long-abandoned live matches', {
|
|
52
|
-
deleted,
|
|
53
|
-
before: before.toISOString(),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return deleted;
|
|
35
|
+
collectAbandonedLive() {
|
|
36
|
+
return this.collectWindow(this.policy.abandonedLiveMaxAgeMs, 'retention deleted long-abandoned live matches', (before) => this.deps.storage.matches.deleteAbandonedLive(before, this.policy.batchSize, true));
|
|
57
37
|
}
|
|
58
38
|
/** The same delete without the roster test, on a window long enough to stand in for it. */
|
|
59
|
-
|
|
60
|
-
|
|
39
|
+
collectSilentLive() {
|
|
40
|
+
return this.collectWindow(this.policy.silentLiveMaxAgeMs, 'retention deleted silent live matches', (before) => this.deps.storage.matches.deleteAbandonedLive(before, this.policy.batchSize, false));
|
|
41
|
+
}
|
|
42
|
+
/** One retention window: a window of 0 is switched off and reads neither the clock nor storage. */
|
|
43
|
+
async collectWindow(maxAgeMs, message, run) {
|
|
44
|
+
if (maxAgeMs <= 0)
|
|
61
45
|
return 0;
|
|
62
|
-
const before = new Date(this.deps.clock.now().getTime() -
|
|
63
|
-
const deleted = await
|
|
64
|
-
if (deleted > 0)
|
|
65
|
-
this.deps.logger.info(
|
|
66
|
-
deleted,
|
|
67
|
-
before: before.toISOString(),
|
|
68
|
-
});
|
|
69
|
-
}
|
|
46
|
+
const before = new Date(this.deps.clock.now().getTime() - maxAgeMs);
|
|
47
|
+
const deleted = await run(before);
|
|
48
|
+
if (deleted > 0)
|
|
49
|
+
this.deps.logger.info(message, { deleted, before: before.toISOString() });
|
|
70
50
|
return deleted;
|
|
71
51
|
}
|
|
72
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RetentionService.js","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AAGA,qGAAqG;AACrG,MAAM,WAAW,GAA2B,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;AA8B9E,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAElC,MAAM,CAAC,MAAM,iBAAiB,GAAoB;IAChD,QAAQ,EAAE,EAAE,GAAG,MAAM;IACrB,qBAAqB,EAAE,EAAE,GAAG,MAAM;IAClC,kBAAkB,EAAE,GAAG,GAAG,MAAM;IAChC,SAAS,EAAE,EAAE;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAgB;IAER,IAAI;IACJ,MAAM;IAFzB,YACmB,IAAsD,EACtD,MAAM,GAAoB,iBAAiB;oBAD3C,IAAI;sBACJ,MAAM;IACtB,CAAC;IAEJ,KAAK,CAAC,OAAO;QACX,OAAO,CACL,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAChC,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACnC,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC,CACjC,CAAA;IACH,CAAC;IAEO,
|
|
1
|
+
{"version":3,"file":"RetentionService.js","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AAGA,qGAAqG;AACrG,MAAM,WAAW,GAA2B,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;AA8B9E,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAElC,MAAM,CAAC,MAAM,iBAAiB,GAAoB;IAChD,QAAQ,EAAE,EAAE,GAAG,MAAM;IACrB,qBAAqB,EAAE,EAAE,GAAG,MAAM;IAClC,kBAAkB,EAAE,GAAG,GAAG,MAAM;IAChC,SAAS,EAAE,EAAE;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAgB;IAER,IAAI;IACJ,MAAM;IAFzB,YACmB,IAAsD,EACtD,MAAM,GAAoB,iBAAiB;oBAD3C,IAAI;sBACJ,MAAM;IACtB,CAAC;IAEJ,KAAK,CAAC,OAAO;QACX,OAAO,CACL,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAChC,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACnC,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC,CACjC,CAAA;IACH,CAAC;IAEO,iBAAiB;QACvB,OAAO,IAAI,CAAC,aAAa,CACvB,IAAI,CAAC,MAAM,CAAC,QAAQ,EACpB,oCAAoC,EACpC,CAAC,MAAM,EAAE,EAAE,CACT,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CACvF,CAAA;IACH,CAAC;IAEO,oBAAoB;QAC1B,OAAO,IAAI,CAAC,aAAa,CACvB,IAAI,CAAC,MAAM,CAAC,qBAAqB,EACjC,+CAA+C,EAC/C,CAAC,MAAM,EAAE,EAAE,CACT,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CACrF,CAAA;IACH,CAAC;IAED,2FAA2F;IACnF,iBAAiB;QACvB,OAAO,IAAI,CAAC,aAAa,CACvB,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAC9B,uCAAuC,EACvC,CAAC,MAAM,EAAE,EAAE,CACT,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CACtF,CAAA;IACH,CAAC;IAED,mGAAmG;IAC3F,KAAK,CAAC,aAAa,CACzB,QAAgB,EAChB,OAAe,EACf,GAAsC;QAEtC,IAAI,QAAQ,IAAI,CAAC;YAAE,OAAO,CAAC,CAAA;QAC3B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAA;QACnE,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,CAAA;QACjC,IAAI,OAAO,GAAG,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;QAC1F,OAAO,OAAO,CAAA;IAChB,CAAC;CACF"}
|
|
@@ -32,6 +32,15 @@ export declare class SnapshotService {
|
|
|
32
32
|
* reconnects next, and `getLatest` is what finds it.
|
|
33
33
|
*/
|
|
34
34
|
private storeCheckpoint;
|
|
35
|
+
/**
|
|
36
|
+
* Write an accepted snapshot: the row, the seat summaries it carries, then the prune.
|
|
37
|
+
*
|
|
38
|
+
* The merge is judged before anything is written: a refusal after the row landed would leave a
|
|
39
|
+
* stored snapshot the caller was told was rejected, with no `snapshot.available` and no verdict.
|
|
40
|
+
* Re-running the settings schema over the merge is what holds the blob to its cap, which is why
|
|
41
|
+
* every upload path comes through here before its write.
|
|
42
|
+
*/
|
|
43
|
+
private store;
|
|
35
44
|
private snapshotOf;
|
|
36
45
|
/**
|
|
37
46
|
* Late-join hints are optional metadata; the snapshot the players are waiting on is not. A
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAA;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"SnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAA;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAQhD;;;;GAIG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHxB,YACmB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,WAAW,EACjC;IAEE,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChF;IAED;;;;;;;;;;;;;;;;OAgBG;YACW,eAAe;IAkB7B;;;;;;;OAOG;YACW,KAAK;IAWnB,OAAO,CAAC,UAAU;IAkBlB;;;OAGG;YACW,oBAAoB;IAYlC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;;;;;;;;;;;;;;;OAgBG;YACW,sBAAsB;IAwBpC;;;;;;OAMG;YACW,iBAAiB;IASzB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAInD;IAEK,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAI9D;CACF;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,GACpD,YAAY,CASd"}
|
|
@@ -2,6 +2,7 @@ import { gameSettingsSchema, } from '@chaos-overlords/contracts';
|
|
|
2
2
|
import { safeParse } from 'valibot';
|
|
3
3
|
import { ConflictError, ForbiddenError, NotFoundError } from '../domain/errors';
|
|
4
4
|
import { authoritativeCandidates } from '../logic/turn-logic';
|
|
5
|
+
import { requireInProgress, requireParticipant, requireTurn } from './guards';
|
|
5
6
|
/**
|
|
6
7
|
* Snapshots kept per live match. Enough to cover a desync being repaired while an earlier one is
|
|
7
8
|
* still being fetched by a straggler, and far fewer than a long match would otherwise accumulate.
|
|
@@ -31,12 +32,8 @@ export class SnapshotService {
|
|
|
31
32
|
reason: 'host_only',
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
if (!isBootstrap && match.status !== 'running' && match.status !== 'desynced') {
|
|
38
|
-
throw new ConflictError('The match is not in progress', { reason: 'match_not_running' });
|
|
39
|
-
}
|
|
35
|
+
requireParticipant(player);
|
|
36
|
+
requireInProgress(match);
|
|
40
37
|
if (request.turn > match.currentTurn) {
|
|
41
38
|
throw new ConflictError('Cannot snapshot a turn that has not opened', {
|
|
42
39
|
reason: 'future_turn',
|
|
@@ -49,26 +46,17 @@ export class SnapshotService {
|
|
|
49
46
|
}
|
|
50
47
|
if (isBootstrap) {
|
|
51
48
|
// Nothing has been reported yet, so there is no consensus a bootstrap could contradict.
|
|
52
|
-
await this.
|
|
53
|
-
await this.publishSeatSummaries(match.id, mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries));
|
|
54
|
-
await this.pruneOldSnapshots(match.id);
|
|
49
|
+
await this.store(match, player.id, request);
|
|
55
50
|
return;
|
|
56
51
|
}
|
|
57
|
-
const turn = await this.deps.storage.turns
|
|
58
|
-
if (!turn)
|
|
59
|
-
throw new NotFoundError('No such turn', { reason: 'unknown_turn' });
|
|
52
|
+
const turn = await requireTurn(this.deps.storage.turns, match.id, request.turn);
|
|
60
53
|
if (turn.status === 'confirmed') {
|
|
61
54
|
await this.storeCheckpoint(match, player, turn, request);
|
|
62
55
|
return;
|
|
63
56
|
}
|
|
64
57
|
this.requireDesyncedTurn(turn);
|
|
65
58
|
await this.requireRepairAuthority(match, player, request.turn, request.stateHash);
|
|
66
|
-
|
|
67
|
-
// snapshot the caller was told was rejected, with no `snapshot.available` and no verdict.
|
|
68
|
-
const gameSettings = mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries);
|
|
69
|
-
await this.deps.storage.snapshots.put(await this.snapshotOf(match, player.id, request));
|
|
70
|
-
await this.publishSeatSummaries(match.id, gameSettings);
|
|
71
|
-
await this.pruneOldSnapshots(match.id);
|
|
59
|
+
await this.store(match, player.id, request);
|
|
72
60
|
await this.publisher.publish(match.id, {
|
|
73
61
|
type: 'snapshot.available',
|
|
74
62
|
payload: {
|
|
@@ -107,15 +95,23 @@ export class SnapshotService {
|
|
|
107
95
|
candidateStateHashes: turn.stateHash === null ? [] : [turn.stateHash],
|
|
108
96
|
});
|
|
109
97
|
}
|
|
98
|
+
await this.store(match, player.id, request);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Write an accepted snapshot: the row, the seat summaries it carries, then the prune.
|
|
102
|
+
*
|
|
103
|
+
* The merge is judged before anything is written: a refusal after the row landed would leave a
|
|
104
|
+
* stored snapshot the caller was told was rejected, with no `snapshot.available` and no verdict.
|
|
105
|
+
* Re-running the settings schema over the merge is what holds the blob to its cap, which is why
|
|
106
|
+
* every upload path comes through here before its write.
|
|
107
|
+
*/
|
|
108
|
+
async store(match, uploadedByPlayerId, request) {
|
|
110
109
|
const gameSettings = mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries);
|
|
111
|
-
await this.deps.storage.snapshots.put(
|
|
110
|
+
await this.deps.storage.snapshots.put(this.snapshotOf(match, uploadedByPlayerId, request));
|
|
112
111
|
await this.publishSeatSummaries(match.id, gameSettings);
|
|
113
112
|
await this.pruneOldSnapshots(match.id);
|
|
114
113
|
}
|
|
115
|
-
|
|
116
|
-
// Re-running the settings schema over the merge is what holds the blob to its cap; it throws
|
|
117
|
-
// before anything is stored, which is why it is called on both paths before the write.
|
|
118
|
-
mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries);
|
|
114
|
+
snapshotOf(match, uploadedByPlayerId, request) {
|
|
119
115
|
return {
|
|
120
116
|
matchId: match.id,
|
|
121
117
|
turn: request.turn,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapshotService.js","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,kBAAkB,GAGnB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"SnapshotService.js","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"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"}
|
|
@@ -81,17 +81,13 @@ export declare class TurnService {
|
|
|
81
81
|
* seats it finds absent, or by a vote on a seat that went quiet before anyone was present to ask,
|
|
82
82
|
* is exactly as blocking as one raised by a kick, and each of those used to leave the countdown
|
|
83
83
|
* running. `openTurn` applies the same rule to a turn opening while a prompt is already up.
|
|
84
|
-
*/
|
|
85
|
-
openTakeoverPrompt(matchId: string, playerId: string, turn: number): Promise<boolean>;
|
|
86
|
-
/**
|
|
87
|
-
* Pause the open turn while players decide what to do with an absent human seat.
|
|
88
84
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
85
|
+
* The pause is a no-op when there is no open turn to stop (during a seal, where the turn the
|
|
86
|
+
* absence was noticed on is already sealed and its successor opens paused instead) and when the
|
|
87
|
+
* clock is stopped already, so a second absent seat neither double-publishes nor disturbs the
|
|
88
|
+
* first pause.
|
|
93
89
|
*/
|
|
94
|
-
|
|
90
|
+
openTakeoverPrompt(matchId: string, playerId: string, turn: number): Promise<boolean>;
|
|
95
91
|
/**
|
|
96
92
|
* Stop the clock of a match whose last active player has just left.
|
|
97
93
|
*
|
|
@@ -102,6 +98,8 @@ export declare class TurnService {
|
|
|
102
98
|
private clearOpenDeadline;
|
|
103
99
|
/** Restart a full turn clock after the final outstanding absence vote closes. */
|
|
104
100
|
resumeAfterTakeoverVotes(matchId: string): Promise<void>;
|
|
101
|
+
/** Tell clients the open turn's restarted clock, then arm the timer that seals it. */
|
|
102
|
+
private announceDeadline;
|
|
105
103
|
/** Re-run the verdict of every unconfirmed turn and the auto-seal of the open one. */
|
|
106
104
|
reevaluate(matchId: string): Promise<void>;
|
|
107
105
|
/** Decide turn `number` from the reports on file. Idempotent and race-safe through CAS. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TurnService.d.ts","sourceRoot":"","sources":["../../src/services/TurnService.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAA;AACnC,OAAO,
|
|
1
|
+
{"version":3,"file":"TurnService.d.ts","sourceRoot":"","sources":["../../src/services/TurnService.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAIL,KAAK,KAAK,EAEX,MAAM,oBAAoB,CAAA;AAI3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAKtD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,CAAA;AAE9C,yFAAyF;AACzF,eAAO,MAAM,UAAU,IAAI,CAAA;AAmB3B;;;;;;;;;;GAUG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAL5B,qGAAqG;IACrG,OAAO,CAAC,mBAAmB,CAAkB;IAE7C,YACmB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,cAAc,EACxC;IAEE,YAAY,CAChB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAkD5B;IAED,yFAAyF;IACnF,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BrF;IAED;;;;;OAKG;YACW,YAAY;IA+D1B;;;;;;OAMG;YACW,sBAAsB;IAepC;;;;;;;OAOG;YACW,QAAQ;IAWtB;;;;OAIG;IACG,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAuD7D;IAED;;;;OAIG;IACG,KAAK,CAAC,KAAK,SAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CA8CtE;YAEa,KAAK;IAiBb,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoD5F;IAED,6FAA6F;YAC/E,oBAAoB;IAWlC;;;;;;;;;;;;;;;;;OAiBG;IACG,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc1F;IAED;;;;;OAKG;IACG,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAExD;YAEa,iBAAiB;IAa/B,iFAAiF;IAC3E,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB7D;IAED,sFAAsF;YACxE,gBAAgB;IAQ9B,sFAAsF;IAChF,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc/C;IAED,2FAA2F;IACrF,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiG3D;IAED;;;;OAIG;YACW,iBAAiB;CA+BhC"}
|