1import { Credentials } from "@distilled.cloud/gcp/Credentials";
2import type { GcpOpContext } from "@distilled.cloud/gcp/pubsublite_v1";
3import * as Effect from "effect/Effect";
4import * as HttpClient from "effect/http/HttpClient";
5import type { AdminReservation } from "./AdminReservation.ts";
6import type { AdminSubscription } from "./AdminSubscription.ts";
7import type { AdminTopic } from "./AdminTopic.ts";
8import { bindGcpHost } from "../Host.ts";
9import { grantFor, type BindingIam } from "../HttpBinding.ts";
10
11type Op<I, A, E> = Effect.Effect<
12 (input: I) => Effect.Effect<A, E>,
13 never,
14 GcpOpContext
15>;
16
17/**
18 * Shared HTTP scaffolding for Pub/Sub Lite bindings.
19 * NOT exported from index.ts.
20 *
21 * Distilled ops are `OperationMethod`s: yield them once at Layer
22 * construction (after providing Credentials + HttpClient) so the inner
23 * runtime Effect is `Effect<A, E>` and does not leak `GcpOpContext`.
24 */
25export const makeReservationHttpBinding = <
26 I extends { name?: string },
27 A,
28 E,
29>(options: {
30 tag: string;
31 iam: BindingIam;
32 operation: Op<I, A, E>;
33}) =>
34 Effect.gen(function* () {
35 const run = yield* options.operation as Effect.Effect<
36 (input: I) => Effect.Effect<A, E>,
37 never,
38 Credentials | HttpClient.HttpClient
39 >;
40 return Effect.fn(function* (reservation: AdminReservation) {
41 yield* bindGcpHost({
42 tag: options.tag,
43 resource: reservation,
44 iam: [grantFor(options.iam, reservation.name)],
45 });
46 const name = yield* reservation.name;
47 return Effect.fn(`${options.tag}(${reservation.LogicalId})`)(function* (
48 request?: Omit<I, "name">,
49 ) {
50 return yield* run({
51 ...(request ?? {}),
52 name: yield* name,
53 } as I);
54 });
55 });
56 });
57
58export const makeTopicNameHttpBinding = <
59 I extends { name?: string },
60 A,
61 E,
62>(options: {
63 tag: string;
64 iam: BindingIam;
65 operation: Op<I, A, E>;
66}) =>
67 Effect.gen(function* () {
68 const run = yield* options.operation as Effect.Effect<
69 (input: I) => Effect.Effect<A, E>,
70 never,
71 Credentials | HttpClient.HttpClient
72 >;
73 return Effect.fn(function* (topic: AdminTopic) {
74 yield* bindGcpHost({
75 tag: options.tag,
76 resource: topic,
77 iam: [grantFor(options.iam, topic.name)],
78 });
79 const name = yield* topic.name;
80 return Effect.fn(`${options.tag}(${topic.LogicalId})`)(function* (
81 request?: Omit<I, "name">,
82 ) {
83 return yield* run({
84 ...(request ?? {}),
85 name: yield* name,
86 } as I);
87 });
88 });
89 });
90
91export const makeTopicStatsHttpBinding = <
92 I extends { topic: string },
93 A,
94 E,
95>(options: {
96 tag: string;
97 iam: BindingIam;
98 operation: Op<I, A, E>;
99}) =>
100 Effect.gen(function* () {
101 const run = yield* options.operation as Effect.Effect<
102 (input: I) => Effect.Effect<A, E>,
103 never,
104 Credentials | HttpClient.HttpClient
105 >;
106 return Effect.fn(function* (topic: AdminTopic) {
107 yield* bindGcpHost({
108 tag: options.tag,
109 resource: topic,
110 iam: [grantFor(options.iam, topic.name)],
111 });
112 const name = yield* topic.name;
113 return Effect.fn(`${options.tag}(${topic.LogicalId})`)(function* (
114 request?: Omit<I, "topic">,
115 ) {
116 return yield* run({
117 ...(request ?? {}),
118 topic: yield* name,
119 } as I);
120 });
121 });
122 });
123
124export const makeSubscriptionHttpBinding = <
125 I extends { name?: string; subscription?: string },
126 A,
127 E,
128>(options: {
129 tag: string;
130 iam: BindingIam;
131 field: "name" | "subscription";
132 operation: Op<I, A, E>;
133}) =>
134 Effect.gen(function* () {
135 const run = yield* options.operation as Effect.Effect<
136 (input: I) => Effect.Effect<A, E>,
137 never,
138 Credentials | HttpClient.HttpClient
139 >;
140 return Effect.fn(function* (subscription: AdminSubscription) {
141 yield* bindGcpHost({
142 tag: options.tag,
143 resource: subscription,
144 iam: [grantFor(options.iam, subscription.name)],
145 });
146 const name = yield* subscription.name;
147 return Effect.fn(`${options.tag}(${subscription.LogicalId})`)(function* (
148 request?: Omit<I, "name" | "subscription">,
149 ) {
150 const resolved = yield* name;
151 const input =
152 options.field === "subscription"
153 ? ({ ...(request ?? {}), subscription: resolved } as I)
154 : ({ ...(request ?? {}), name: resolved } as I);
155 return yield* run(input);
156 });
157 });
158 });