1import { Effect, Layer, Schema } from "effect";
2import {
3 Rpc,
4 RpcGroup,
5 RpcSerialization,
6 RpcServer,
7} from "effect/rpc";
8import { Job, JobId } from "./Job.ts";
9import { JobNotifications } from "./JobNotifications.ts";
10import { JobStorage } from "./JobStorage.ts";
11
12export class JobNotFound extends Schema.TaggedClass<JobNotFound>()(
13 "JobNotFound",
14 { jobId: JobId },
15) {}
16
17export class GetJobFailed extends Schema.TaggedClass<GetJobFailed>()(
18 "GetJobFailed",
19 { message: Schema.String },
20) {}
21
22export class PutJobFailed extends Schema.TaggedClass<PutJobFailed>()(
23 "PutJobFailed",
24 { message: Schema.String },
25) {}
26
27const getJob = Rpc.make("getJob", {
28 success: Job,
29 error: Schema.Union([JobNotFound, GetJobFailed]),
30 payload: {
31 jobId: JobId,
32 },
33});
34
35const createJob = Rpc.make("createJob", {
36 success: JobId,
37 error: PutJobFailed,
38 payload: {
39 content: Schema.String,
40 },
41});
42
43export class JobRpcs extends RpcGroup.make(getJob, createJob) {}
44
45export const JobRpcsLive = JobRpcs.toLayer(
46 Effect.gen(function* () {
47 const jobService = yield* JobStorage;
48 const notifications = yield* JobNotifications;
49
50 return {
51 getJob: ({ jobId }) =>
52 jobService.getJob(jobId).pipe(
53 Effect.mapError(
54 (error) =>
55 new GetJobFailed({
56 message: error.message,
57 }),
58 ),
59 Effect.flatMap((job) =>
60 job ? Effect.succeed(job) : Effect.fail(new JobNotFound({ jobId })),
61 ),
62 ),
63 createJob: ({ content }) =>
64 Effect.gen(function* () {
65 const jobId = crypto.randomUUID();
66 const job = yield* jobService.putJob({
67 id: jobId,
68 content,
69 });
70 yield* notifications.notifyJobCreated(job);
71 return job.id;
72 }).pipe(
73 Effect.mapError(
74 (error) =>
75 new PutJobFailed({
76 message: error.message,
77 }),
78 ),
79 ),
80 };
81 }),
82);
83
84export const JobRpcHttpEffect = RpcServer.toHttpEffect(JobRpcs).pipe(
85 Effect.provide(Layer.mergeAll(JobRpcsLive, RpcSerialization.layerJson)),
86);