| 1 | import * as Cloudflare from "@/Cloudflare"; |
| 2 | import { Layer } from "effect"; |
| 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Path from "effect/Path"; |
| 5 | import { HttpRouter } from "effect/http"; |
| 6 | import * as Etag from "effect/http/Etag"; |
| 7 | import * as HttpPlatform from "effect/http/HttpPlatform"; |
| 8 | import * as HttpApi from "effect/http-api/HttpApi"; |
| 9 | import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder"; |
| 10 | import * as HttpApiGroup from "effect/http-api/HttpApiGroup"; |
| 11 | |
| 12 | import { createTask, decodeTask, encodeTask, getTask, Task } from "./api.ts"; |
| 13 | |
| 14 | const HttpPlatformStub = Layer.succeed(HttpPlatform.HttpPlatform, { |
| 15 | platform: "web", |
| 16 | compression: { |
| 17 | algorithms: new Set<HttpPlatform.CompressionAlgorithm>(), |
| 18 | compressResponse: (response) => Effect.succeed(response), |
| 19 | }, |
| 20 | fileResponse: () => Effect.die("HttpPlatform.fileResponse not supported"), |
| 21 | fileWebResponse: () => |
| 22 | Effect.die("HttpPlatform.fileWebResponse not supported"), |
| 23 | }); |
| 24 | |
| 25 | export class TasksDOGroup extends HttpApiGroup.make("TasksDO") |
| 26 | .add(getTask) |
| 27 | .add(createTask) {} |
| 28 | |
| 29 | export class TaskDOApi extends HttpApi.make("TaskDOApi").add(TasksDOGroup) {} |
| 30 | |
| 31 | /** |
| 32 | * Durable Object backing the `createTaskDO` / `getTaskDO` endpoints. |
| 33 | * Persists tasks in the DO's transactional storage and exposes simple |
| 34 | * RPC methods that the Worker calls from its HttpApi handlers. |
| 35 | */ |
| 36 | export default class TasksObject extends Cloudflare.DurableObject<TasksObject>()( |
| 37 | "TasksObject", |
| 38 | Effect.gen(function* () { |
| 39 | const state = yield* Cloudflare.DurableObjectState; |
| 40 | |
| 41 | return Effect.gen(function* () { |
| 42 | const tasksGroup = HttpApiBuilder.group( |
| 43 | TaskDOApi, |
| 44 | "TasksDO", |
| 45 | (handlers) => |
| 46 | handlers |
| 47 | .handle("getTask", ({ params }) => |
| 48 | state.storage |
| 49 | .get<Task>(params.id) |
| 50 | .pipe(Effect.flatMap(decodeTask), Effect.orDie), |
| 51 | ) |
| 52 | .handle("createTask", ({ payload }) => { |
| 53 | const id = crypto.randomUUID(); |
| 54 | const task = new Task({ |
| 55 | id, |
| 56 | title: payload.title, |
| 57 | completed: false, |
| 58 | }); |
| 59 | return state.storage |
| 60 | .put(id, encodeTask(task)) |
| 61 | .pipe(Effect.as(task)); |
| 62 | }), |
| 63 | ); |
| 64 | |
| 65 | return { |
| 66 | fetch: HttpApiBuilder.layer(TaskDOApi).pipe( |
| 67 | Layer.provide(tasksGroup), |
| 68 | Layer.provide([Etag.layer, HttpPlatformStub, Path.layer]), |
| 69 | HttpRouter.toHttpEffect, |
| 70 | ), |
| 71 | }; |
| 72 | }); |
| 73 | }), |
| 74 | ) {} |