1import { Credentials, CredentialsFromEnv } from "@distilled.cloud/fly-io";
2import type * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as FetchHttpClient from "effect/http/FetchHttpClient";
6import type * as HttpClient from "effect/http/HttpClient";
7import { bindFlyApiToken } from "./Credentials.ts";
8import type { RuntimeContext } from "../RuntimeContext.ts";
9import type { Sprite } from "./Sprite.ts";
10
11/**
12 * Shared scaffolding for HTTP-backed Fly Sprite bindings.
13 *
14 * Captures ambient credentials during stack-eval (so Actions work
15 * in-process) and `yield*`s the org token plus sprite `name` so
16 * RuntimeContext.set runs. Runtime calls inside a deployed host read
17 * `FLY_API_TOKEN` via {@link CredentialsFromEnv} after Platform copies
18 * `runtimeContext.env` onto the host.
19 *
20 * NOT exported from `index.ts`.
21 */
22export const makeHttpSpriteBinding = <Client>(options: {
23 makeClient: (auth: SpriteAuth, spriteName: Effect.Effect<string>) => Client;
24}) =>
25 Effect.gen(function* () {
26 const context = yield* Effect.context<
27 Credentials | HttpClient.HttpClient
28 >();
29
30 return Effect.fn(function* (sprite: Sprite) {
31 yield* bindFlyApiToken().pipe(Effect.provideContext(context));
32 const name = yield* sprite.name;
33 return options.makeClient(makeSpriteAuth(context), name);
34 });
35 });
36
37export interface SpriteAuth {
38 authorize: <A, E>(
39 eff: Effect.Effect<A, E, Credentials | HttpClient.HttpClient>,
40 ) => Effect.Effect<A, E, RuntimeContext>;
41}
42
43export const makeSpriteAuth = (
44 ambient: Context.Context<Credentials | HttpClient.HttpClient>,
45): SpriteAuth => ({
46 authorize: (eff) => {
47 if (globalThis.__ALCHEMY_RUNTIME__) {
48 return eff.pipe(
49 Effect.provide(
50 Layer.mergeAll(CredentialsFromEnv, FetchHttpClient.layer),
51 ),
52 );
53 }
54 return eff.pipe(Effect.provideContext(ambient));
55 },
56});