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
+33
View File
@@ -44,6 +44,39 @@ enum DiplomacyLetterState {
REPLACED
}
enum InputEventStatus {
PENDING
PROCESSING
SUCCEEDED
FAILED
}
enum InputEventTarget {
API
ENGINE
}
model InputEvent {
sequence BigInt @id @default(autoincrement())
requestId String @unique @map("request_id")
target InputEventTarget
eventType String @map("event_type")
payload Json @default(dbgenerated("'{}'::jsonb"))
actorUserId String? @map("actor_user_id")
status InputEventStatus @default(PENDING)
result Json?
error String?
attempts Int @default(0)
lockedBy String? @map("locked_by")
leaseUntil DateTime? @map("lease_until")
createdAt DateTime @default(now()) @map("created_at")
processingAt DateTime? @map("processing_at")
completedAt DateTime? @map("completed_at")
@@index([target, status, sequence])
@@map("input_event")
}
model WorldState {
id Int @id @default(autoincrement())
scenarioCode String @map("scenario_code")
@@ -0,0 +1,25 @@
CREATE TYPE "InputEventStatus" AS ENUM ('PENDING', 'PROCESSING', 'SUCCEEDED', 'FAILED');
CREATE TYPE "InputEventTarget" AS ENUM ('API', 'ENGINE');
CREATE TABLE "input_event" (
"sequence" BIGSERIAL NOT NULL,
"request_id" TEXT NOT NULL,
"target" "InputEventTarget" NOT NULL,
"event_type" TEXT NOT NULL,
"payload" JSONB NOT NULL DEFAULT '{}'::jsonb,
"actor_user_id" TEXT,
"status" "InputEventStatus" NOT NULL DEFAULT 'PENDING',
"result" JSONB,
"error" TEXT,
"attempts" INTEGER NOT NULL DEFAULT 0,
"locked_by" TEXT,
"lease_until" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"processing_at" TIMESTAMP(3),
"completed_at" TIMESTAMP(3),
CONSTRAINT "input_event_pkey" PRIMARY KEY ("sequence")
);
CREATE UNIQUE INDEX "input_event_request_id_key" ON "input_event"("request_id");
CREATE INDEX "input_event_target_status_sequence_idx" ON "input_event"("target", "status", "sequence");
+1
View File
@@ -25,4 +25,5 @@ export interface DatabaseClient {
inheritanceLog: GamePrisma.InheritanceLogDelegate;
inheritanceResult: GamePrisma.InheritanceResultDelegate;
inheritanceUserState: GamePrisma.InheritanceUserStateDelegate;
inputEvent: GamePrisma.InputEventDelegate;
}