feat: implement input event system with durable command handling

- Introduced InputEvent model with status tracking (PENDING, PROCESSING, SUCCEEDED, FAILED) and unique request IDs.
- Added DatabaseTurnDaemonTransport for sending commands and handling idempotency.
- Implemented executeInputEvent function to manage input event lifecycle and error handling.
- Created DatabaseTurnDaemonCommandQueue for managing command processing and lease recovery.
- Enhanced turn daemon lifecycle to support atomic command execution and error recovery.
- Added tests for input event atomicity, command queuing, and error handling scenarios.
This commit is contained in:
2026-07-25 05:36:34 +00:00
parent c5da507df9
commit 5040691d7c
34 changed files with 1587 additions and 448 deletions
+32 -25
View File
@@ -1,10 +1,6 @@
import { z } from 'zod';
import type {
TurnDaemonCommand,
TurnDaemonCommandType,
TurnDaemonCommandByType,
} from '@sammo-ts/common';
import type { TurnDaemonCommand, TurnDaemonCommandType, TurnDaemonCommandByType } from '@sammo-ts/common';
export type TurnDaemonCommandEnvelope = {
requestId: string;
@@ -161,22 +157,21 @@ const zSetNationMeta = z.object({
expectedUpdatedAt: z.string().optional(),
});
const zAdjustGeneralResources = z
.object({
type: z.literal('adjustGeneralResources'),
reason: z.string().optional(),
adjustments: z
.array(
z
.object({
generalId: zFiniteNumber,
goldDelta: zFiniteNumber.optional(),
riceDelta: zFiniteNumber.optional(),
})
.refine((value) => value.goldDelta !== undefined || value.riceDelta !== undefined)
)
.min(1),
});
const zAdjustGeneralResources = z.object({
type: z.literal('adjustGeneralResources'),
reason: z.string().optional(),
adjustments: z
.array(
z
.object({
generalId: zFiniteNumber,
goldDelta: zFiniteNumber.optional(),
riceDelta: zFiniteNumber.optional(),
})
.refine((value) => value.goldDelta !== undefined || value.riceDelta !== undefined)
)
.min(1),
});
const zAdjustGeneralMeta = z.object({
type: z.literal('adjustGeneralMeta'),
@@ -430,10 +425,22 @@ const normalizeGetStatus: CommandNormalizer<'getStatus'> = (envelope) => {
};
};
const normalizeRun: CommandNormalizer<'run'> = (envelope) => parseWith(zRun, envelope.command);
const normalizePause: CommandNormalizer<'pause'> = (envelope) => parseWith(zPause, envelope.command);
const normalizeResume: CommandNormalizer<'resume'> = (envelope) => parseWith(zResume, envelope.command);
const normalizeShutdown: CommandNormalizer<'shutdown'> = (envelope) => parseWith(zShutdown, envelope.command);
const normalizeRun: CommandNormalizer<'run'> = (envelope) => {
const command = parseWith(zRun, envelope.command);
return command ? { ...command, requestId: envelope.requestId } : null;
};
const normalizePause: CommandNormalizer<'pause'> = (envelope) => {
const command = parseWith(zPause, envelope.command);
return command ? { ...command, requestId: envelope.requestId } : null;
};
const normalizeResume: CommandNormalizer<'resume'> = (envelope) => {
const command = parseWith(zResume, envelope.command);
return command ? { ...command, requestId: envelope.requestId } : null;
};
const normalizeShutdown: CommandNormalizer<'shutdown'> = (envelope) => {
const command = parseWith(zShutdown, envelope.command);
return command ? { ...command, requestId: envelope.requestId } : null;
};
const normalizers: CommandNormalizerMap = {
auctionFinalize: normalizeAuctionFinalize,