1import * as Data from "effect/Data";
2import type * as Effect from "effect/Effect";
3import * as Binding from "../../Binding.ts";
4import type { RuntimeContext } from "../../RuntimeContext.ts";
5import type { Service } from "./Service.ts";
6
7/** Request options for {@link InvokeServiceClient.fetch}. */
8export interface InvokeServiceRequestInit {
9 /** HTTP method. @default "GET" */
10 method?: string;
11 /** Extra request headers. `Authorization` is always set by the binding. */
12 headers?: Record<string, string>;
13 /** Request body; objects are not serialized — pass a string. */
14 body?: string | Uint8Array;
15}
16
17/** A fully buffered response from the bound service. */
18export interface InvokeServiceResponse {
19 /** HTTP status code. */
20 readonly status: number;
21 /** Response headers (lower-cased names). */
22 readonly headers: Readonly<Record<string, string>>;
23 /** The response body as text. */
24 readonly text: Effect.Effect<string>;
25 /** The response body parsed as JSON. */
26 readonly json: Effect.Effect<unknown, InvokeServiceError>;
27}
28
29/** Runtime client returned by {@link InvokeService}. */
30export interface InvokeServiceClient {
31 /**
32 * Call the bound service at `path` (relative to its `uri`) with a
33 * Google-signed ID token for the host's runtime service account.
34 */
35 fetch(
36 path: string,
37 init?: InvokeServiceRequestInit,
38 ): Effect.Effect<InvokeServiceResponse, InvokeServiceError, RuntimeContext>;
39}
40
41/**
42 * Minting the ID token, reaching the service, or decoding its response
43 * failed. Non-2xx responses are not errors — inspect `status`.
44 */
45export class InvokeServiceError extends Data.TaggedError(
46 "GCP.Run.InvokeServiceError",
47)<{
48 message: string;
49 cause?: unknown;
50}> {}
51
52/**
53 * Runtime binding that calls a private Cloud Run {@link Service} from
54 * another Cloud Run host with Google-signed identity — the GCP analog of
55 * `AWS.Lambda.InvokeFunction`.
56 *
57 * At deploy time it grants `roles/run.invoker` on the target service to
58 * the host's runtime service account and binds the target's `uri` into
59 * the host. At runtime it mints an ID token (audience = the target `uri`)
60 * from the metadata server, caches it until shortly before expiry, and
61 * sends it as `Authorization: Bearer`. The target keeps
62 * `invokerIamDisabled` off, so anything without a token gets `403`.
63 *
64 * Provide {@link InvokeServiceHttp}.
65 *
66 * ### Calling a private service
67 * **Example:** Forward a request to a private backend
68 * ```typescript
69 * export default class Gateway extends GCP.Function<Gateway>()(
70 * "Gateway",
71 * { main: import.meta.url, invokerIamDisabled: true },
72 * Effect.gen(function* () {
73 * const quotes = yield* GCP.Run.InvokeService(Quotes);
74 * return {
75 * fetch: Effect.gen(function* () {
76 * const response = yield* quotes.fetch("/quote").pipe(Effect.orDie);
77 * return HttpServerResponse.text(yield* response.text, {
78 * status: response.status,
79 * });
80 * }),
81 * };
82 * }).pipe(Effect.provide(GCP.Run.InvokeServiceHttp)),
83 * ) {}
84 * ```
85 *
86 * **Example:** POST a JSON body
87 * ```typescript
88 * const response = yield* orders.fetch("/orders", {
89 * method: "POST",
90 * headers: { "content-type": "application/json" },
91 * body: JSON.stringify({ sku: "abc", quantity: 1 }),
92 * });
93 * const order = yield* response.json;
94 * ```
95 *
96 * @binding
97 * @category Run
98 */
99export interface InvokeService extends Binding.Service<
100 InvokeService,
101 "GCP.Run.InvokeService",
102 (service: Service) => Effect.Effect<InvokeServiceClient>
103> {}
104
105export const InvokeService = Binding.Service<InvokeService>(
106 "GCP.Run.InvokeService",
107);