1import * as GCP from "@/GCP";
2import * as Effect from "effect/Effect";
3import * as HttpServerResponse from "effect/http/HttpServerResponse";
4
5/** Bucket whose uploads Eventarc routes to the service. */
6export const Drops = GCP.Storage.Bucket("EventarcDrops", {
7 location: "US-CENTRAL1",
8 forceDestroy: true,
9});
10/** Bucket the service records each event in (unwatched, so no loops). */
11export const Markers = GCP.Storage.Bucket("EventarcMarkers", {
12 location: "US-CENTRAL1",
13 forceDestroy: true,
14});
15
16export const markerFor = (object: string) =>
17 `${object.replaceAll("/", "_")}.json`;
18
19/**
20 * Effect-native Cloud Run service (default private ingress) receiving
21 * Storage finalize events through an Eventarc trigger.
22 * Deployed from {@link ../EventSource.test.ts}.
23 */
24export default class EventarcService extends GCP.Function<EventarcService>()(
25 "EventarcService",
26 { main: import.meta.url, location: "us-central1" },
27 Effect.gen(function* () {
28 const drops = yield* Drops;
29 const markers = yield* Markers;
30 const putMarker = yield* GCP.Storage.PutObject(markers);
31
32 yield* GCP.Eventarc.consumeEvents(
33 "Drops",
34 {
35 eventFilters: [
36 {
37 attribute: "type",
38 value: "google.cloud.storage.object.v1.finalized",
39 },
40 { attribute: "bucket", value: drops.bucketName },
41 ],
42 },
43 (event) =>
44 Effect.gen(function* () {
45 const data = event.data as { name?: string; bucket?: string };
46 yield* putMarker({
47 name: markerFor(data.name ?? "unknown"),
48 body: JSON.stringify({
49 id: event.id,
50 type: event.type,
51 subject: event.subject,
52 bucket: data.bucket,
53 name: data.name,
54 }),
55 }).pipe(Effect.orDie);
56 }),
57 );
58
59 return { fetch: Effect.succeed(HttpServerResponse.text("ok")) };
60 }).pipe(
61 Effect.provide(GCP.Storage.PutObjectHttp),
62 Effect.provide(GCP.Run.EventarcEventSource),
63 ),
64) {}