@chaos-overlords/kernel 0.1.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.
Files changed (104) hide show
  1. package/LICENSE +674 -0
  2. package/NOTICE +21 -0
  3. package/README.md +28 -0
  4. package/dist/domain/entities.d.ts +91 -0
  5. package/dist/domain/entities.d.ts.map +1 -0
  6. package/dist/domain/entities.js +9 -0
  7. package/dist/domain/entities.js.map +1 -0
  8. package/dist/domain/errors.d.ts +30 -0
  9. package/dist/domain/errors.d.ts.map +1 -0
  10. package/dist/domain/errors.js +45 -0
  11. package/dist/domain/errors.js.map +1 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +7 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/logic/canonical-json.d.ts +17 -0
  17. package/dist/logic/canonical-json.d.ts.map +1 -0
  18. package/dist/logic/canonical-json.js +44 -0
  19. package/dist/logic/canonical-json.js.map +1 -0
  20. package/dist/logic/crypto.d.ts +29 -0
  21. package/dist/logic/crypto.d.ts.map +1 -0
  22. package/dist/logic/crypto.js +76 -0
  23. package/dist/logic/crypto.js.map +1 -0
  24. package/dist/logic/index.d.ts +5 -0
  25. package/dist/logic/index.d.ts.map +1 -0
  26. package/dist/logic/index.js +5 -0
  27. package/dist/logic/index.js.map +1 -0
  28. package/dist/logic/password.d.ts +4 -0
  29. package/dist/logic/password.d.ts.map +1 -0
  30. package/dist/logic/password.js +38 -0
  31. package/dist/logic/password.js.map +1 -0
  32. package/dist/logic/turn-logic.d.ts +52 -0
  33. package/dist/logic/turn-logic.d.ts.map +1 -0
  34. package/dist/logic/turn-logic.js +101 -0
  35. package/dist/logic/turn-logic.js.map +1 -0
  36. package/dist/ports/index.d.ts +3 -0
  37. package/dist/ports/index.d.ts.map +1 -0
  38. package/dist/ports/index.js +3 -0
  39. package/dist/ports/index.js.map +1 -0
  40. package/dist/ports/runtime.d.ts +38 -0
  41. package/dist/ports/runtime.d.ts.map +1 -0
  42. package/dist/ports/runtime.js +7 -0
  43. package/dist/ports/runtime.js.map +1 -0
  44. package/dist/ports/storage.d.ts +141 -0
  45. package/dist/ports/storage.d.ts.map +1 -0
  46. package/dist/ports/storage.js +2 -0
  47. package/dist/ports/storage.js.map +1 -0
  48. package/dist/services/AuthService.d.ts +13 -0
  49. package/dist/services/AuthService.d.ts.map +1 -0
  50. package/dist/services/AuthService.js +24 -0
  51. package/dist/services/AuthService.js.map +1 -0
  52. package/dist/services/EventPublisher.d.ts +20 -0
  53. package/dist/services/EventPublisher.d.ts.map +1 -0
  54. package/dist/services/EventPublisher.js +37 -0
  55. package/dist/services/EventPublisher.js.map +1 -0
  56. package/dist/services/LobbyService.d.ts +45 -0
  57. package/dist/services/LobbyService.d.ts.map +1 -0
  58. package/dist/services/LobbyService.js +366 -0
  59. package/dist/services/LobbyService.js.map +1 -0
  60. package/dist/services/MatchQueryService.d.ts +24 -0
  61. package/dist/services/MatchQueryService.d.ts.map +1 -0
  62. package/dist/services/MatchQueryService.js +99 -0
  63. package/dist/services/MatchQueryService.js.map +1 -0
  64. package/dist/services/RateLimiter.d.ts +28 -0
  65. package/dist/services/RateLimiter.d.ts.map +1 -0
  66. package/dist/services/RateLimiter.js +48 -0
  67. package/dist/services/RateLimiter.js.map +1 -0
  68. package/dist/services/RetentionService.d.ts +23 -0
  69. package/dist/services/RetentionService.d.ts.map +1 -0
  70. package/dist/services/RetentionService.js +36 -0
  71. package/dist/services/RetentionService.js.map +1 -0
  72. package/dist/services/SnapshotService.d.ts +40 -0
  73. package/dist/services/SnapshotService.d.ts.map +1 -0
  74. package/dist/services/SnapshotService.js +137 -0
  75. package/dist/services/SnapshotService.js.map +1 -0
  76. package/dist/services/TurnService.d.ts +63 -0
  77. package/dist/services/TurnService.d.ts.map +1 -0
  78. package/dist/services/TurnService.js +392 -0
  79. package/dist/services/TurnService.js.map +1 -0
  80. package/dist/services/createKernel.d.ts +23 -0
  81. package/dist/services/createKernel.d.ts.map +1 -0
  82. package/dist/services/createKernel.js +22 -0
  83. package/dist/services/createKernel.js.map +1 -0
  84. package/dist/services/deps.d.ts +11 -0
  85. package/dist/services/deps.d.ts.map +1 -0
  86. package/dist/services/deps.js +2 -0
  87. package/dist/services/deps.js.map +1 -0
  88. package/dist/services/index.d.ts +10 -0
  89. package/dist/services/index.d.ts.map +1 -0
  90. package/dist/services/index.js +10 -0
  91. package/dist/services/index.js.map +1 -0
  92. package/dist/testing/InMemoryStorage.d.ts +28 -0
  93. package/dist/testing/InMemoryStorage.d.ts.map +1 -0
  94. package/dist/testing/InMemoryStorage.js +279 -0
  95. package/dist/testing/InMemoryStorage.js.map +1 -0
  96. package/dist/testing/fakes.d.ts +38 -0
  97. package/dist/testing/fakes.d.ts.map +1 -0
  98. package/dist/testing/fakes.js +44 -0
  99. package/dist/testing/fakes.js.map +1 -0
  100. package/dist/testing/index.d.ts +3 -0
  101. package/dist/testing/index.d.ts.map +1 -0
  102. package/dist/testing/index.js +3 -0
  103. package/dist/testing/index.js.map +1 -0
  104. package/package.json +68 -0
@@ -0,0 +1,392 @@
1
+ import { foreignOps, } from '@chaos-overlords/contracts';
2
+ import { activePlayers, humanParticipants } from '../domain/entities';
3
+ import { ConflictError, ForbiddenError, NotFoundError, ValidationError } from '../domain/errors';
4
+ import { hashOrderDocument, hashOrderSet } from '../logic/crypto';
5
+ import { allActiveReady, evaluateConsensus, turnDeadline } from '../logic/turn-logic';
6
+ /** Turns are numbered from 1; 0 is the lobby's `currentTurn`, before any turn exists. */
7
+ export const FIRST_TURN = 1;
8
+ /**
9
+ * The simultaneous-turn barrier. Players submit orders privately; the turn seals when every
10
+ * active player is ready or the deadline passes; the sealed set becomes readable and the next
11
+ * turn opens at once. Clients resolve the turn locally and report the resulting state hash, and
12
+ * the server confirms the turn on agreement or flags a desync.
13
+ *
14
+ * Every state change here is a compare-and-swap, so concurrent callers (the last player's ready
15
+ * racing the timer, two players' reports landing together) produce exactly one seal and one
16
+ * verdict. Sealing is a CAS followed by `completeSeal`, which is idempotent from any point: that is
17
+ * what lets `sweep` finish a seal whose process died halfway through instead of stranding the match.
18
+ */
19
+ export class TurnService {
20
+ deps;
21
+ publisher;
22
+ constructor(deps, publisher) {
23
+ this.deps = deps;
24
+ this.publisher = publisher;
25
+ }
26
+ async submitOrders(principal, number, request) {
27
+ const { match, player } = principal;
28
+ requireRunning(match);
29
+ if (player.status !== 'active' && player.status !== 'takeoverPending') {
30
+ throw new ForbiddenError('You are no longer part of this match', { reason: 'not_active' });
31
+ }
32
+ if (number !== match.currentTurn) {
33
+ throw new ConflictError(`Turn ${match.currentTurn} is the open turn`, {
34
+ reason: 'not_current_turn',
35
+ currentTurn: match.currentTurn,
36
+ });
37
+ }
38
+ assertOwnOps(request, player.slot);
39
+ await this.restorePendingPlayer(player.id, match.id);
40
+ const previous = await this.deps.storage.turns.getOrders(match.id, number, player.id);
41
+ const ordersHash = await hashOrderDocument(request.orders);
42
+ const accepted = await this.deps.storage.turns.submitOrders(match.id, number, player.id, {
43
+ orders: request.orders,
44
+ ordersHash,
45
+ ready: request.ready,
46
+ submittedAt: this.deps.clock.now(),
47
+ });
48
+ if (!accepted) {
49
+ throw new ConflictError('The turn is no longer accepting orders', { reason: 'turn_not_open' });
50
+ }
51
+ if (previous?.ready !== request.ready) {
52
+ await this.publisher.publish(match.id, {
53
+ type: 'turn.readiness',
54
+ payload: { turn: number, playerId: player.id, ready: request.ready },
55
+ });
56
+ }
57
+ if (request.ready)
58
+ await this.trySeal(match.id, number, 'ready');
59
+ return { turn: number, orders: request.orders, ready: request.ready, ordersHash };
60
+ }
61
+ /** Seal `number` if its trigger condition holds. Returns whether THIS call sealed it. */
62
+ async trySeal(matchId, number, trigger) {
63
+ const match = await this.deps.storage.matches.get(matchId);
64
+ if (match?.status !== 'running')
65
+ return false;
66
+ const turn = await this.deps.storage.turns.get(matchId, number);
67
+ if (turn?.status !== 'open')
68
+ return false;
69
+ if (trigger === 'deadline') {
70
+ if (turn.deadlineAt === null || turn.deadlineAt.getTime() > this.deps.clock.now().getTime()) {
71
+ return false;
72
+ }
73
+ }
74
+ else {
75
+ const [players, orders] = await Promise.all([
76
+ this.deps.storage.players.listByMatch(matchId),
77
+ this.deps.storage.turns.listOrders(matchId, number),
78
+ ]);
79
+ if (!allActiveReady(players, orders))
80
+ return false;
81
+ }
82
+ const won = await this.deps.storage.turns.transition(matchId, number, ['open'], {
83
+ status: 'sealed',
84
+ sealedAt: this.deps.clock.now(),
85
+ });
86
+ if (!won)
87
+ return false;
88
+ this.deps.logger.info('turn sealed', { matchId, turn: number, trigger });
89
+ await this.completeSeal(match, number);
90
+ return true;
91
+ }
92
+ /**
93
+ * Everything that follows the seal's compare-and-swap: freeze the participant set and its digest,
94
+ * announce it, and open the next turn. Each step is conditional on its own predecessor, so a call
95
+ * on a turn that is already complete changes nothing and a call on one that stopped halfway
96
+ * finishes it. Orders are immutable from the CAS onwards, so the digest it derives is stable.
97
+ */
98
+ async completeSeal(match, number) {
99
+ const turn = await this.deps.storage.turns.get(match.id, number);
100
+ if (turn?.status === 'open')
101
+ return false;
102
+ if (!turn) {
103
+ // No row at all: the match is live and pointed at a turn that was never created, which is
104
+ // what `start` leaves behind when it dies between the status change and opening turn 1.
105
+ // There is no seal to finish, only the missing turn to open — `currentTurn` names it, except
106
+ // at 0, which is the lobby's value and means turn 1 was never reached.
107
+ return this.openTurn(match, Math.max(number, FIRST_TURN));
108
+ }
109
+ let advanced = false;
110
+ if (turn.orderSetHash === null || turn.sealedSlots === null) {
111
+ const [orders, players] = await Promise.all([
112
+ this.deps.storage.turns.listOrders(match.id, number),
113
+ this.deps.storage.players.listByMatch(match.id),
114
+ ]);
115
+ for (const player of activePlayers(players)) {
116
+ const row = orders.find((candidate) => candidate.playerId === player.id);
117
+ if (row?.ordersHash !== null)
118
+ continue;
119
+ if (await this.deps.storage.players.transitionStatus(player.id, ['active'], 'takeoverPending')) {
120
+ await this.publisher.publish(match.id, {
121
+ type: 'match.takeoverVoteRequested',
122
+ payload: { playerId: player.id, turn: number },
123
+ });
124
+ }
125
+ }
126
+ const currentPlayers = await this.deps.storage.players.listByMatch(match.id);
127
+ const slotOf = new Map(humanParticipants(currentPlayers).map((player) => [player.id, player.slot]));
128
+ const sealedSlots = [];
129
+ const entries = [];
130
+ for (const row of orders) {
131
+ const slot = slotOf.get(row.playerId);
132
+ if (slot === undefined || row.ordersHash === null)
133
+ continue;
134
+ sealedSlots.push({ playerId: row.playerId, slot });
135
+ entries.push({ slot, ordersHash: row.ordersHash });
136
+ }
137
+ const orderSetHash = await hashOrderSet(entries);
138
+ const frozen = await this.deps.storage.turns.transition(match.id, number, [turn.status], {
139
+ status: turn.status,
140
+ orderSetHash,
141
+ sealedSlots,
142
+ });
143
+ if (frozen) {
144
+ advanced = true;
145
+ await this.publisher.publish(match.id, {
146
+ type: 'turn.sealed',
147
+ payload: { turn: number, orderSetHash },
148
+ });
149
+ }
150
+ }
151
+ return (await this.openTurn(match, number + 1)) || advanced;
152
+ }
153
+ /**
154
+ * Opens turn `number` for every active player and arms its deadline. Idempotent: the insert is
155
+ * refused if the turn already exists, and only the caller that created it announces it. Returns
156
+ * whether this call created the turn.
157
+ */
158
+ async openTurn(match, number) {
159
+ const openedAt = this.deps.clock.now();
160
+ const deadlineAt = turnDeadline(openedAt, match.settings.turnTimerSeconds);
161
+ const players = humanParticipants(await this.deps.storage.players.listByMatch(match.id));
162
+ const created = await this.deps.storage.turns.open({
163
+ matchId: match.id,
164
+ number,
165
+ status: 'open',
166
+ openedAt,
167
+ deadlineAt,
168
+ sealedAt: null,
169
+ orderSetHash: null,
170
+ sealedSlots: null,
171
+ }, players.map((player) => player.id));
172
+ // A desynced match counts: a repaired seal must still point `currentTurn` at the turn that is
173
+ // actually open, even though nobody may submit to it until the pause lifts.
174
+ await this.deps.storage.matches.transition(match.id, ['running', 'desynced'], {
175
+ currentTurn: number,
176
+ updatedAt: openedAt,
177
+ });
178
+ if (created) {
179
+ await this.publisher.publish(match.id, {
180
+ type: 'turn.opened',
181
+ payload: { turn: number, deadlineAt: deadlineAt?.toISOString() ?? null },
182
+ });
183
+ }
184
+ if (deadlineAt) {
185
+ await this.deps.scheduler.schedule({ matchId: match.id, turn: number, dueAt: deadlineAt });
186
+ }
187
+ return created;
188
+ }
189
+ /**
190
+ * The safety net behind the scheduler and behind any interrupted seal: seal every open turn whose
191
+ * deadline has passed, then finish every seal that stopped before opening its successor. Runtimes
192
+ * call this on an interval (Node) or a cron (Cloudflare).
193
+ */
194
+ async sweep(limit = 100) {
195
+ const expired = await this.deps.storage.turns.listExpiredOpen(this.deps.clock.now(), limit);
196
+ let sealed = 0;
197
+ for (const { matchId, number } of expired) {
198
+ if (await this.trySeal(matchId, number, 'deadline'))
199
+ sealed += 1;
200
+ }
201
+ // A seal in flight looks stalled for the moment between its compare-and-swap and the next turn
202
+ // opening, so this also runs against healthy matches. `completeSeal` changes nothing there, and
203
+ // only a call that actually had work left to do is reported.
204
+ let repaired = 0;
205
+ for (const { matchId, number } of await this.deps.storage.turns.listStalledSeals(limit)) {
206
+ const match = await this.deps.storage.matches.get(matchId);
207
+ if (!match)
208
+ continue;
209
+ if (await this.completeSeal(match, number)) {
210
+ this.deps.logger.warn('finished an interrupted seal', { matchId, turn: number });
211
+ repaired += 1;
212
+ }
213
+ }
214
+ return { sealed, repaired };
215
+ }
216
+ async report(principal, number, request) {
217
+ const { match, player } = principal;
218
+ if (match.status !== 'running' && match.status !== 'desynced') {
219
+ throw new ConflictError('The match is not in progress', { reason: 'match_not_running' });
220
+ }
221
+ if (player.status !== 'active' && player.status !== 'takeoverPending') {
222
+ throw new ForbiddenError('You are no longer part of this match', { reason: 'not_active' });
223
+ }
224
+ const turn = await this.deps.storage.turns.get(match.id, number);
225
+ if (!turn)
226
+ throw new NotFoundError('No such turn', { reason: 'unknown_turn' });
227
+ if (turn.status === 'open') {
228
+ throw new ConflictError('The turn has not been sealed yet', { reason: 'turn_open' });
229
+ }
230
+ await this.restorePendingPlayer(player.id, match.id);
231
+ await this.deps.storage.turns.upsertReport({
232
+ matchId: match.id,
233
+ turn: number,
234
+ playerId: player.id,
235
+ stateHash: request.stateHash,
236
+ finished: request.finished,
237
+ reportedAt: this.deps.clock.now(),
238
+ });
239
+ await this.settle(match.id, number);
240
+ }
241
+ /** Authenticated turn activity wins the race with an AI vote and restores the human seat. */
242
+ async restorePendingPlayer(playerId, matchId) {
243
+ if (await this.deps.storage.players.transitionStatus(playerId, ['takeoverPending'], 'active')) {
244
+ await this.publisher.publish(matchId, {
245
+ type: 'match.takeoverVoteCancelled',
246
+ payload: { playerId },
247
+ });
248
+ }
249
+ }
250
+ /** Re-run the verdict of every unconfirmed turn and the auto-seal of the open one. */
251
+ async reevaluate(matchId) {
252
+ const match = await this.deps.storage.matches.get(matchId);
253
+ if (!match || (match.status !== 'running' && match.status !== 'desynced'))
254
+ return;
255
+ for (const turn of await this.deps.storage.turns.listUnsettled(matchId)) {
256
+ await this.settle(matchId, turn.number);
257
+ }
258
+ await this.trySeal(matchId, match.currentTurn, 'ready');
259
+ }
260
+ /** Decide turn `number` from the reports on file. Idempotent and race-safe through CAS. */
261
+ async settle(matchId, number) {
262
+ const [match, turn] = await Promise.all([
263
+ this.deps.storage.matches.get(matchId),
264
+ this.deps.storage.turns.get(matchId, number),
265
+ ]);
266
+ if (!match || !turn || turn.status === 'open' || turn.status === 'confirmed')
267
+ return;
268
+ const [players, reports, snapshot] = await Promise.all([
269
+ this.deps.storage.players.listByMatch(matchId),
270
+ this.deps.storage.turns.listReports(matchId, number),
271
+ this.deps.storage.snapshots.get(matchId, number),
272
+ ]);
273
+ const verdict = evaluateConsensus(players, reports, snapshot?.stateHash ?? null);
274
+ const now = this.deps.clock.now();
275
+ if (verdict.kind === 'confirmed') {
276
+ const won = await this.deps.storage.turns.transition(matchId, number, ['sealed', 'desynced'], {
277
+ status: 'confirmed',
278
+ });
279
+ if (!won)
280
+ return;
281
+ await this.publisher.publish(matchId, {
282
+ type: 'turn.confirmed',
283
+ payload: { turn: number, stateHash: verdict.stateHash },
284
+ });
285
+ if (match.status === 'desynced')
286
+ await this.resumeAfterDesync(matchId, now);
287
+ if (verdict.finished &&
288
+ (await this.deps.storage.matches.transition(matchId, ['running', 'desynced'], {
289
+ status: 'finished',
290
+ updatedAt: now,
291
+ }))) {
292
+ // The seal opened the successor before anyone had reported, so a finished match is left
293
+ // holding an open turn with a live deadline. Nothing can be submitted to it (every write
294
+ // requires a running match) but it would sit in `listExpiredOpen` forever, and a client
295
+ // reading the match view would see a turn still counting down after the game ended.
296
+ await this.deps.storage.turns.rescheduleDeadline(matchId, turn.number + 1, null);
297
+ await this.publisher.publish(matchId, {
298
+ type: 'match.statusChanged',
299
+ payload: { status: 'finished' },
300
+ });
301
+ }
302
+ return;
303
+ }
304
+ if (verdict.kind === 'desynced') {
305
+ const won = await this.deps.storage.turns.transition(matchId, number, ['sealed'], {
306
+ status: 'desynced',
307
+ });
308
+ if (!won)
309
+ return;
310
+ this.deps.logger.warn('turn desynced', { matchId, turn: number });
311
+ await this.publisher.publish(matchId, {
312
+ type: 'turn.desynced',
313
+ payload: {
314
+ turn: number,
315
+ reports: verdict.reports,
316
+ candidateStateHashes: verdict.candidateStateHashes,
317
+ },
318
+ });
319
+ if (await this.deps.storage.matches.transition(matchId, ['running'], {
320
+ status: 'desynced',
321
+ updatedAt: now,
322
+ })) {
323
+ await this.publisher.publish(matchId, {
324
+ type: 'match.statusChanged',
325
+ payload: { status: 'desynced' },
326
+ });
327
+ }
328
+ }
329
+ }
330
+ /**
331
+ * Lift a desync pause once nothing is unsettled. Orders were refused for the whole pause while
332
+ * the open turn's deadline kept running, so the turn would otherwise seal empty the moment the
333
+ * match resumes: its clock restarts here, and clients are told the new deadline.
334
+ */
335
+ async resumeAfterDesync(matchId, now) {
336
+ if ((await this.deps.storage.turns.listUnsettled(matchId)).length > 0)
337
+ return;
338
+ const match = await this.deps.storage.matches.get(matchId);
339
+ if (!match)
340
+ return;
341
+ if (!(await this.deps.storage.matches.transition(matchId, ['desynced'], {
342
+ status: 'running',
343
+ updatedAt: now,
344
+ }))) {
345
+ return;
346
+ }
347
+ await this.publisher.publish(matchId, {
348
+ type: 'match.statusChanged',
349
+ payload: { status: 'running' },
350
+ });
351
+ const deadlineAt = turnDeadline(now, match.settings.turnTimerSeconds);
352
+ if (deadlineAt === null)
353
+ return; // A match without a turn timer has no clock to restart.
354
+ if (!(await this.deps.storage.turns.rescheduleDeadline(matchId, match.currentTurn, deadlineAt))) {
355
+ return;
356
+ }
357
+ await this.publisher.publish(matchId, {
358
+ type: 'turn.deadlineExtended',
359
+ payload: { turn: match.currentTurn, deadlineAt: deadlineAt.toISOString() },
360
+ });
361
+ await this.deps.scheduler.schedule({ matchId, turn: match.currentTurn, dueAt: deadlineAt });
362
+ }
363
+ }
364
+ /**
365
+ * Refuse a document whose ops act for a slot other than the submitter's.
366
+ *
367
+ * The sealed set attributes every op to the slot it was submitted from, and that attribution is
368
+ * the one clients apply, so an op naming a different player can only be a client bug or an attempt
369
+ * to act as somebody else. Catching it here keeps the two attributions from ever disagreeing, and
370
+ * it is the one piece of order semantics the server can judge without knowing the rules.
371
+ */
372
+ function assertOwnOps(request, slot) {
373
+ const foreign = foreignOps(request.orders, slot);
374
+ if (foreign.length === 0)
375
+ return;
376
+ throw new ValidationError('Orders may only act for your own slot', {
377
+ reason: 'foreign_slot_ops',
378
+ slot,
379
+ ops: foreign.slice(0, 8).map((op) => ({ op: op.op, player: op.player })),
380
+ });
381
+ }
382
+ function requireRunning(match) {
383
+ if (match.status === 'desynced') {
384
+ throw new ConflictError('The match is paused until the host uploads a snapshot', {
385
+ reason: 'match_desynced',
386
+ });
387
+ }
388
+ if (match.status !== 'running') {
389
+ throw new ConflictError('The match is not in progress', { reason: 'match_not_running' });
390
+ }
391
+ }
392
+ //# sourceMappingURL=TurnService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TurnService.js","sourceRoot":"","sources":["../../src/services/TurnService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { AuthService } from './AuthService';
2
+ import type { KernelDeps } from './deps';
3
+ import { LobbyService, type LobbyServiceOptions } from './LobbyService';
4
+ import { MatchQueryService } from './MatchQueryService';
5
+ import { type RetentionPolicy, RetentionService } from './RetentionService';
6
+ import { SnapshotService } from './SnapshotService';
7
+ import { TurnService } from './TurnService';
8
+ export interface Kernel {
9
+ deps: KernelDeps;
10
+ auth: AuthService;
11
+ query: MatchQueryService;
12
+ lobby: LobbyService;
13
+ turns: TurnService;
14
+ snapshots: SnapshotService;
15
+ retention: RetentionService;
16
+ }
17
+ export interface KernelOptions extends LobbyServiceOptions {
18
+ /** Overrides `DEFAULT_RETENTION`; a runtime maps its own configuration onto it. */
19
+ retention?: RetentionPolicy;
20
+ }
21
+ /** Wires the services once; both runtime facades call this with their own ports. */
22
+ export declare function createKernel(deps: KernelDeps, options?: KernelOptions): Kernel;
23
+ //# sourceMappingURL=createKernel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createKernel.d.ts","sourceRoot":"","sources":["../../src/services/createKernel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAExC,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,KAAK,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,EAAE,iBAAiB,CAAA;IACxB,KAAK,EAAE,YAAY,CAAA;IACnB,KAAK,EAAE,WAAW,CAAA;IAClB,SAAS,EAAE,eAAe,CAAA;IAC1B,SAAS,EAAE,gBAAgB,CAAA;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,mBAAmB;IACxD,mFAAmF;IACnF,SAAS,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAYlF"}
@@ -0,0 +1,22 @@
1
+ import { AuthService } from './AuthService';
2
+ import { EventPublisher } from './EventPublisher';
3
+ import { LobbyService } from './LobbyService';
4
+ import { MatchQueryService } from './MatchQueryService';
5
+ import { RetentionService } from './RetentionService';
6
+ import { SnapshotService } from './SnapshotService';
7
+ import { TurnService } from './TurnService';
8
+ /** Wires the services once; both runtime facades call this with their own ports. */
9
+ export function createKernel(deps, options = {}) {
10
+ const publisher = new EventPublisher(deps);
11
+ const turns = new TurnService(deps, publisher);
12
+ return {
13
+ deps,
14
+ auth: new AuthService(deps.storage),
15
+ query: new MatchQueryService(deps.storage),
16
+ lobby: new LobbyService(deps, publisher, turns, options),
17
+ turns,
18
+ snapshots: new SnapshotService(deps, publisher, turns),
19
+ retention: new RetentionService(deps, options.retention),
20
+ };
21
+ }
22
+ //# sourceMappingURL=createKernel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createKernel.js","sourceRoot":"","sources":["../../src/services/createKernel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,YAAY,EAA4B,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAwB,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAiB3C,oFAAoF;AACpF,MAAM,UAAU,YAAY,CAAC,IAAgB,EAAE,UAAyB,EAAE;IACxE,MAAM,SAAS,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,CAAA;IAC1C,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC9C,OAAO;QACL,IAAI;QACJ,IAAI,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;QACnC,KAAK,EAAE,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC;QAC1C,KAAK,EAAE,IAAI,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC;QACxD,KAAK;QACL,SAAS,EAAE,IAAI,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC;QACtD,SAAS,EAAE,IAAI,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC;KACzD,CAAA;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Clock, DeadlineScheduler, EventNotifier, Logger } from '../ports/runtime';
2
+ import type { MultiplayerStorage } from '../ports/storage';
3
+ /** Everything the services need, injected once by the runtime facade. */
4
+ export interface KernelDeps {
5
+ storage: MultiplayerStorage;
6
+ notifier: EventNotifier;
7
+ scheduler: DeadlineScheduler;
8
+ clock: Clock;
9
+ logger: Logger;
10
+ }
11
+ //# sourceMappingURL=deps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/services/deps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAE1D,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,kBAAkB,CAAA;IAC3B,QAAQ,EAAE,aAAa,CAAA;IACvB,SAAS,EAAE,iBAAiB,CAAA;IAC5B,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/services/deps.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ export * from './AuthService';
2
+ export * from './deps';
3
+ export * from './EventPublisher';
4
+ export * from './LobbyService';
5
+ export * from './MatchQueryService';
6
+ export * from './RateLimiter';
7
+ export * from './RetentionService';
8
+ export * from './SnapshotService';
9
+ export * from './TurnService';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA"}
@@ -0,0 +1,10 @@
1
+ export * from './AuthService';
2
+ export * from './deps';
3
+ export * from './EventPublisher';
4
+ export * from './LobbyService';
5
+ export * from './MatchQueryService';
6
+ export * from './RateLimiter';
7
+ export * from './RetentionService';
8
+ export * from './SnapshotService';
9
+ export * from './TurnService';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA"}
@@ -0,0 +1,28 @@
1
+ import type { MatchStatus } from '@chaos-overlords/contracts';
2
+ import type { EventRepository, MatchRepository, MultiplayerStorage, PlayerRepository, SnapshotRepository, TurnRepository } from '../ports/storage';
3
+ /**
4
+ * A reference implementation of the storage ports with the same semantics as the SQL ones:
5
+ * single-threaded JS makes every method atomic, and the uniqueness a database index would enforce
6
+ * (match id, join code, token hash, turn and event keys) is enforced here by hand so a service can
7
+ * be tested against the same refusals. The storage conformance suite runs against it, which is what
8
+ * keeps the claim honest.
9
+ */
10
+ export declare class InMemoryStorage implements MultiplayerStorage {
11
+ private readonly matchRows;
12
+ private readonly playerRows;
13
+ private readonly turnRows;
14
+ private readonly orderRows;
15
+ private readonly reportRows;
16
+ private readonly snapshotRows;
17
+ private readonly eventRows;
18
+ readonly matches: MatchRepository;
19
+ readonly players: PlayerRepository;
20
+ readonly turns: TurnRepository;
21
+ readonly snapshots: SnapshotRepository;
22
+ readonly events: EventRepository;
23
+ /** What a cascading delete does in SQL: the match row and everything keyed by it. */
24
+ private deleteMatch;
25
+ /** Test hook: the match statuses on file, for assertions that bypass the services. */
26
+ statusOf(matchId: string): MatchStatus | undefined;
27
+ }
28
+ //# sourceMappingURL=InMemoryStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InMemoryStorage.d.ts","sourceRoot":"","sources":["../../src/testing/InMemoryStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,WAAW,EAAc,MAAM,4BAA4B,CAAA;AAUvF,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACf,MAAM,kBAAkB,CAAA;AAEzB;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,kBAAkB;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2B;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4B;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;IAC3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8B;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;IAEhE,QAAQ,CAAC,OAAO,EAAE,eAAe,CAwDhC;IAED,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAgDjC;IAED,QAAQ,CAAC,KAAK,EAAE,cAAc,CAiF7B;IAED,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAqBrC;IAED,QAAQ,CAAC,MAAM,EAAE,eAAe,CAc/B;IAED,qFAAqF;IACrF,OAAO,CAAC,WAAW;IAoBnB,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;CAGnD"}