import { Effect, Layer, Schema, Struct } from "effect";import * as SqlClient from "effect/unstable/sql/SqlClient";import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts";import { DeleteProjectionThreadWorkUnitsInput, ListProjectionThreadWorkUnitsInput, ProjectionThreadWorkUnit, ProjectionThreadWorkUnitRepository, type ProjectionThreadWorkUnitRepositoryShape,} from "../Services/ProjectionThreadWorkUnits.ts"; const ProjectionThreadWorkUnitDbRowSchema = ProjectionThreadWorkUnit.mapFields( Struct.assign({ providerRefs: Schema.NullOr( Schema.fromJsonString(ProjectionThreadWorkUnit.fields.providerRefs), ), }),); function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) { return (cause: unknown) => Schema.isSchemaError(cause) ? toPersistenceDecodeError(decodeOperation)(cause) : toPersistenceSqlError(sqlOperation)(cause);} const makeProjectionThreadWorkUnitRepository = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const upsertProjectionThreadWorkUnitRow = SqlSchema.void({ Request: ProjectionThreadWorkUnit, execute: (row) => sql` INSERT INTO projection_thread_work_units ( work_unit_id, thread_id, turn_id, parent_work_unit_id, kind, state, title, detail, spawned_by_activity_id, provider_refs_json, started_at, updated_at, completed_at ) VALUES ( ${row.workUnitId}, ${row.threadId}, ${row.turnId}, ${row.parentWorkUnitId}, ${row.kind}, ${row.state}, ${row.title}, ${row.detail}, ${row.spawnedByActivityId}, ${row.providerRefs ? JSON.stringify(row.providerRefs) : null}, ${row.startedAt}, ${row.updatedAt}, ${row.completedAt} ) ON CONFLICT (work_unit_id) DO UPDATE SET thread_id = excluded.thread_id, turn_id = excluded.turn_id, parent_work_unit_id = excluded.parent_work_unit_id, kind = excluded.kind, state = excluded.state, title = excluded.title, detail = excluded.detail, spawned_by_activity_id = excluded.spawned_by_activity_id, provider_refs_json = excluded.provider_refs_json, started_at = excluded.started_at, updated_at = excluded.updated_at, completed_at = excluded.completed_at `, }); const listProjectionThreadWorkUnitRows = SqlSchema.findAll({ Request: ListProjectionThreadWorkUnitsInput, Result: ProjectionThreadWorkUnitDbRowSchema, execute: ({ threadId }) => sql` SELECT work_unit_id AS "workUnitId", thread_id AS "threadId", turn_id AS "turnId", parent_work_unit_id AS "parentWorkUnitId", kind, state, title, detail, spawned_by_activity_id AS "spawnedByActivityId", provider_refs_json AS "providerRefs", started_at AS "startedAt", updated_at AS "updatedAt", completed_at AS "completedAt" FROM projection_thread_work_units WHERE thread_id = ${threadId} ORDER BY started_at ASC, work_unit_id ASC `, }); const deleteProjectionThreadWorkUnitRows = SqlSchema.void({ Request: DeleteProjectionThreadWorkUnitsInput, execute: ({ threadId }) => sql` DELETE FROM projection_thread_work_units WHERE thread_id = ${threadId} `, }); const upsert: ProjectionThreadWorkUnitRepositoryShape["upsert"] = (row) => upsertProjectionThreadWorkUnitRow(row).pipe( Effect.mapError(toPersistenceSqlError("ProjectionThreadWorkUnitRepository.upsert:query")), ); const listByThreadId: ProjectionThreadWorkUnitRepositoryShape["listByThreadId"] = (input) => listProjectionThreadWorkUnitRows(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionThreadWorkUnitRepository.listByThreadId:query", "ProjectionThreadWorkUnitRepository.listByThreadId:decodeRows", ), ), Effect.map((rows) => rows.map((row) => ({ workUnitId: row.workUnitId, threadId: row.threadId, turnId: row.turnId, parentWorkUnitId: row.parentWorkUnitId, kind: row.kind, state: row.state, title: row.title, detail: row.detail, spawnedByActivityId: row.spawnedByActivityId, ...(row.providerRefs !== null ? { providerRefs: row.providerRefs } : {}), startedAt: row.startedAt, updatedAt: row.updatedAt, completedAt: row.completedAt, })), ), ); const deleteByThreadId: ProjectionThreadWorkUnitRepositoryShape["deleteByThreadId"] = (input) => deleteProjectionThreadWorkUnitRows(input).pipe( Effect.mapError( toPersistenceSqlError("ProjectionThreadWorkUnitRepository.deleteByThreadId:query"), ), ); return { upsert, listByThreadId, deleteByThreadId, } satisfies ProjectionThreadWorkUnitRepositoryShape;}); export const ProjectionThreadWorkUnitRepositoryLive = Layer.effect( ProjectionThreadWorkUnitRepository, makeProjectionThreadWorkUnitRepository,);
import { Effect, Layer, Schema, Struct } from "effect";
import * as SqlClient from "effect/unstable/sql/SqlClient";
import * as SqlSchema from "effect/unstable/sql/SqlSchema";
import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts";
import {
DeleteProjectionThreadWorkUnitsInput,
ListProjectionThreadWorkUnitsInput,
ProjectionThreadWorkUnit,
ProjectionThreadWorkUnitRepository,
type ProjectionThreadWorkUnitRepositoryShape,
} from "../Services/ProjectionThreadWorkUnits.ts";
const ProjectionThreadWorkUnitDbRowSchema = ProjectionThreadWorkUnit.mapFields(
Struct.assign({
providerRefs: Schema.NullOr(
Schema.fromJsonString(ProjectionThreadWorkUnit.fields.providerRefs),
),
}),
);
function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
return (cause: unknown) =>
Schema.isSchemaError(cause)
? toPersistenceDecodeError(decodeOperation)(cause)
: toPersistenceSqlError(sqlOperation)(cause);
}
const makeProjectionThreadWorkUnitRepository = Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;
const upsertProjectionThreadWorkUnitRow = SqlSchema.void({
Request: ProjectionThreadWorkUnit,
execute: (row) => sql`
INSERT INTO projection_thread_work_units (
work_unit_id,
thread_id,
turn_id,
parent_work_unit_id,
kind,
state,
title,
detail,
spawned_by_activity_id,
provider_refs_json,
started_at,
updated_at,
completed_at
)
VALUES (
${row.workUnitId},
${row.threadId},
${row.turnId},
${row.parentWorkUnitId},
${row.kind},
${row.state},
${row.title},
${row.detail},
${row.spawnedByActivityId},
${row.providerRefs ? JSON.stringify(row.providerRefs) : null},
${row.startedAt},
${row.updatedAt},
${row.completedAt}
)
ON CONFLICT (work_unit_id)
DO UPDATE SET
thread_id = excluded.thread_id,
turn_id = excluded.turn_id,
parent_work_unit_id = excluded.parent_work_unit_id,
kind = excluded.kind,
state = excluded.state,
title = excluded.title,
detail = excluded.detail,
spawned_by_activity_id = excluded.spawned_by_activity_id,
provider_refs_json = excluded.provider_refs_json,
started_at = excluded.started_at,
updated_at = excluded.updated_at,
completed_at = excluded.completed_at
`,
});
const listProjectionThreadWorkUnitRows = SqlSchema.findAll({
Request: ListProjectionThreadWorkUnitsInput,
Result: ProjectionThreadWorkUnitDbRowSchema,
execute: ({ threadId }) => sql`
SELECT
work_unit_id AS "workUnitId",
thread_id AS "threadId",
turn_id AS "turnId",
parent_work_unit_id AS "parentWorkUnitId",
kind,
state,
title,
detail,
spawned_by_activity_id AS "spawnedByActivityId",
provider_refs_json AS "providerRefs",
started_at AS "startedAt",
updated_at AS "updatedAt",
completed_at AS "completedAt"
FROM projection_thread_work_units
WHERE thread_id = ${threadId}
ORDER BY started_at ASC, work_unit_id ASC
`,
});
const deleteProjectionThreadWorkUnitRows = SqlSchema.void({
Request: DeleteProjectionThreadWorkUnitsInput,
execute: ({ threadId }) => sql`
DELETE FROM projection_thread_work_units
WHERE thread_id = ${threadId}
`,
});
const upsert: ProjectionThreadWorkUnitRepositoryShape["upsert"] = (row) =>
upsertProjectionThreadWorkUnitRow(row).pipe(
Effect.mapError(toPersistenceSqlError("ProjectionThreadWorkUnitRepository.upsert:query")),
);
const listByThreadId: ProjectionThreadWorkUnitRepositoryShape["listByThreadId"] = (input) =>
listProjectionThreadWorkUnitRows(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"ProjectionThreadWorkUnitRepository.listByThreadId:query",
"ProjectionThreadWorkUnitRepository.listByThreadId:decodeRows",
),
),
Effect.map((rows) =>
rows.map((row) => ({
workUnitId: row.workUnitId,
threadId: row.threadId,
turnId: row.turnId,
parentWorkUnitId: row.parentWorkUnitId,
kind: row.kind,
state: row.state,
title: row.title,
detail: row.detail,
spawnedByActivityId: row.spawnedByActivityId,
...(row.providerRefs !== null ? { providerRefs: row.providerRefs } : {}),
startedAt: row.startedAt,
updatedAt: row.updatedAt,
completedAt: row.completedAt,
})),
),
);
const deleteByThreadId: ProjectionThreadWorkUnitRepositoryShape["deleteByThreadId"] = (input) =>
deleteProjectionThreadWorkUnitRows(input).pipe(
Effect.mapError(
toPersistenceSqlError("ProjectionThreadWorkUnitRepository.deleteByThreadId:query"),
),
);
return {
upsert,
listByThreadId,
deleteByThreadId,
} satisfies ProjectionThreadWorkUnitRepositoryShape;
});
export const ProjectionThreadWorkUnitRepositoryLive = Layer.effect(
ProjectionThreadWorkUnitRepository,
makeProjectionThreadWorkUnitRepository,
);