@chaos-overlords/worker 0.9.5 → 0.9.6

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 (2) hide show
  1. package/package.json +8 -8
  2. package/src/MatchHub.ts +17 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaos-overlords/worker",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "Cloudflare Workers runtime facade: the shared Hono app over D1, with one Durable Object per match for SSE fan-out and turn-deadline alarms.",
5
5
  "keywords": [
6
6
  "chaos-overlords",
@@ -47,17 +47,17 @@
47
47
  "provenance": true
48
48
  },
49
49
  "dependencies": {
50
- "@chaos-overlords/bug-reports": "0.9.5",
51
- "@chaos-overlords/contracts": "0.9.5",
52
- "@chaos-overlords/kernel": "0.9.5",
53
- "@chaos-overlords/server": "0.9.5",
54
- "@chaos-overlords/storage": "0.9.5",
50
+ "@chaos-overlords/bug-reports": "0.9.6",
51
+ "@chaos-overlords/contracts": "0.9.6",
52
+ "@chaos-overlords/kernel": "0.9.6",
53
+ "@chaos-overlords/server": "0.9.6",
54
+ "@chaos-overlords/storage": "0.9.6",
55
55
  "drizzle-orm": "^0.45.2",
56
56
  "hono": "^4.13.8"
57
57
  },
58
58
  "devDependencies": {
59
- "@chaos-overlords/client": "0.9.5",
60
- "@chaos-overlords/conformance": "0.9.5",
59
+ "@chaos-overlords/client": "0.9.6",
60
+ "@chaos-overlords/conformance": "0.9.6",
61
61
  "@cloudflare/vitest-pool-workers": "^0.22.0",
62
62
  "@cloudflare/workers-types": "^5.20260920.1",
63
63
  "typescript": "^7.0.2",
package/src/MatchHub.ts CHANGED
@@ -47,6 +47,15 @@ function retryFloor(
47
47
  return Math.max(dueAtMs, fired.dueAtMs + EARLY_DEADLINE_RETRY_MS)
48
48
  }
49
49
 
50
+ /** Whether a pending deadline is still exactly the one read earlier, due time included. */
51
+ function isSameDeadline(pending: PendingDeadline | undefined, seen: PendingDeadline): boolean {
52
+ return (
53
+ pending?.matchId === seen.matchId &&
54
+ pending.turn === seen.turn &&
55
+ pending.dueAtMs === seen.dueAtMs
56
+ )
57
+ }
58
+
50
59
  /**
51
60
  * One instance per match. It holds the open event streams of that match (so a notification from
52
61
  * any Worker isolate reaches every subscriber) and the alarm for the open turn's deadline. The
@@ -160,6 +169,14 @@ export class MatchHub {
160
169
  const stillDue =
161
170
  match?.status === 'running' && turn?.status === 'open' && turn.deadlineAt !== null
162
171
  if (stillDue) return
172
+ // Asked again after the D1 reads, which open the object's input gate: a ready seal in another
173
+ // isolate can arm its successor's deadline in between. Deleting on the first answer threw that
174
+ // deadline away, and the new turn then counted down to zero on every screen and sat there until
175
+ // a player ended it or the cron swept it. Nothing awaits between this read and the delete
176
+ // except the object's own storage, which keeps the gate shut.
177
+ if (!isSameDeadline(await this.state.storage.get<PendingDeadline>(DEADLINE_KEY), current)) {
178
+ return
179
+ }
163
180
  await this.state.storage.delete(DEADLINE_KEY)
164
181
  await this.state.storage.deleteAlarm()
165
182
  }