1import { assert, it } from "@effect/vitest";
2import { RunId, ThreadId, TurnItemId } from "@t3tools/contracts";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as SqlClient from "effect/sql/SqlClient";
6
7import * as SqlitePersistence from "../persistence/Sqlite.ts";
8import * as TurnItemPositionStore from "./TurnItemPositionStore.ts";
9
10const layerTest = TurnItemPositionStore.layer.pipe(
11 Layer.provideMerge(SqlitePersistence.layerMemory),
12);
13
14it.effect("reuses an item's position without attempting another allocation insert", () =>
15 Effect.gen(function* () {
16 const sql = yield* SqlClient.SqlClient;
17 const positions = yield* TurnItemPositionStore.TurnItemPositionStoreV2;
18 const input = {
19 threadId: ThreadId.make("thread:position-reuse"),
20 turnItemId: TurnItemId.make("item:streaming"),
21 runId: null,
22 };
23 const first = yield* positions.allocate(input);
24 assert.equal(first, 1);
25
26 // BEFORE INSERT runs even when ON CONFLICT would discard the allocation.
27 yield* sql`
28 CREATE TEMP TRIGGER reject_redundant_position_insert
29 BEFORE INSERT ON orchestration_v2_turn_item_positions
30 WHEN EXISTS (
31 SELECT 1 FROM orchestration_v2_turn_item_positions
32 WHERE thread_id = NEW.thread_id AND turn_item_id = NEW.turn_item_id
33 )
34 BEGIN
35 SELECT RAISE(ABORT, 'Existing item positions must be reused');
36 END
37 `;
38
39 const next = yield* positions.allocate({
40 ...input,
41 turnItemId: TurnItemId.make("item:next"),
42 });
43 assert.equal(next, first + 1);
44 assert.equal(yield* positions.allocate(input), first);
45 assert.equal(
46 yield* positions.allocate({
47 ...input,
48 runId: RunId.make("run:moved"),
49 runOrdinal: 2,
50 }),
51 first,
52 );
53 assert.equal(
54 yield* positions.allocate({ ...input, threadId: ThreadId.make("thread:other") }),
55 1,
56 );
57 }).pipe(Effect.provide(layerTest)),
58);