feat(team-mode): add team tasklist store with tests
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { expect, test } from "bun:test"
|
||||
import { readFile } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
|
||||
import { getTasksDir, resolveBaseDir } from "../team-registry"
|
||||
import { createTask } from "./store"
|
||||
import { createTaskInput, createTasklistFixture } from "./test-support"
|
||||
|
||||
test("createTask assigns distinct ids during concurrent creation", async () => {
|
||||
// given
|
||||
const fixture = await createTasklistFixture()
|
||||
|
||||
try {
|
||||
// when
|
||||
const [firstTask, secondTask] = await Promise.all([
|
||||
createTask(fixture.teamRunId, createTaskInput({ subject: "first task" }), fixture.config),
|
||||
createTask(fixture.teamRunId, createTaskInput({ subject: "second task" }), fixture.config),
|
||||
])
|
||||
|
||||
const tasksDirectory = getTasksDir(resolveBaseDir(fixture.config), fixture.teamRunId)
|
||||
const watermarkContent = await readFile(path.join(tasksDirectory, ".highwatermark"), "utf8")
|
||||
const sortedIds = [firstTask.id, secondTask.id].sort((leftId, rightId) => Number(leftId) - Number(rightId))
|
||||
|
||||
// then
|
||||
expect(sortedIds).toEqual(["1", "2"])
|
||||
expect(watermarkContent.trim()).toBe("2")
|
||||
} finally {
|
||||
await fixture.cleanup()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
import { mkdir, readFile } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
|
||||
import type { TeamModeConfig } from "../../../config/schema/team-mode"
|
||||
import { getTasksDir, resolveBaseDir } from "../team-registry"
|
||||
import { atomicWrite, withLock } from "../team-state-store/locks"
|
||||
import { TaskSchema } from "../types"
|
||||
import type { Task } from "../types"
|
||||
|
||||
const HIGH_WATERMARK_FILE = ".highwatermark"
|
||||
|
||||
async function readHighWatermark(watermarkPath: string): Promise<number> {
|
||||
try {
|
||||
const watermarkContent = (await readFile(watermarkPath, "utf8")).trim()
|
||||
const parsedWatermark = Number.parseInt(watermarkContent, 10)
|
||||
return Number.isInteger(parsedWatermark) && parsedWatermark >= 0 ? parsedWatermark : 0
|
||||
} catch {
|
||||
await atomicWrite(watermarkPath, "0")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTask(
|
||||
teamRunId: string,
|
||||
taskInput: Omit<Task, "id" | "createdAt" | "updatedAt" | "version">,
|
||||
config: TeamModeConfig,
|
||||
): Promise<Task> {
|
||||
const tasksDirectory = getTasksDir(resolveBaseDir(config), teamRunId)
|
||||
await mkdir(tasksDirectory, { recursive: true, mode: 0o700 })
|
||||
await mkdir(path.join(tasksDirectory, "claims"), { recursive: true, mode: 0o700 })
|
||||
|
||||
return withLock(path.join(tasksDirectory, ".lock"), async () => {
|
||||
const watermarkPath = path.join(tasksDirectory, HIGH_WATERMARK_FILE)
|
||||
const nextTaskId = (await readHighWatermark(watermarkPath)) + 1
|
||||
await atomicWrite(watermarkPath, String(nextTaskId))
|
||||
|
||||
const now = Date.now()
|
||||
const task = TaskSchema.parse({
|
||||
...taskInput,
|
||||
version: 1,
|
||||
id: String(nextTaskId),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
|
||||
await atomicWrite(
|
||||
path.join(tasksDirectory, `${task.id}.json`),
|
||||
`${JSON.stringify(task, null, 2)}\n`,
|
||||
)
|
||||
|
||||
return task
|
||||
}, { ownerTag: `create-task:${teamRunId}` })
|
||||
}
|
||||
Reference in New Issue
Block a user