1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { Space } from "./Space.ts";
5
6/**
7 * Shared scaffolding for the re:Post Private runtime bindings.
8 *
9 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
10 * thin `Layer.effect(Cap, makeRePostSpaceHttpBinding({ … }))` over the
11 * builder below. Everything except the operation and the IAM action list is
12 * boilerplate: every re:Post Private data-plane operation is scoped to a
13 * space, so the runtime callable injects the bound {@link Space}'s `spaceId`
14 * and the deploy-time half grants `actions` on the space's ARN (re:Post
15 * Private has a single `space` IAM resource type — channel- and
16 * admin-scoped actions are all authorized against the space ARN).
17 */
18export const makeRePostSpaceHttpBinding = <
19 I extends { spaceId: string },
20 A,
21 E,
22 R,
23>(options: {
24 /** Fully-qualified binding tag, e.g. `AWS.RePostSpace.SendInvites`. */
25 tag: string;
26 /** The distilled operation; `spaceId` is injected from the bound space. */
27 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
28 /** IAM actions granted on the space ARN. */
29 actions: readonly string[];
30}) =>
31 Effect.gen(function* () {
32 const op = yield* options.operation;
33
34 return Effect.fn(function* (space: Space) {
35 const spaceId = yield* space.spaceId;
36 if (!globalThis.__ALCHEMY_RUNTIME__) {
37 const host = yield* Binding.Host;
38 if (isBindingHost(host)) {
39 yield* host.bind`Allow(${host}, ${options.tag}(${space}))`({
40 policyStatements: [
41 {
42 Effect: "Allow",
43 Action: [...options.actions],
44 Resource: [space.spaceArn],
45 },
46 ],
47 });
48 }
49 }
50 return Effect.fn(`${options.tag}(${space.LogicalId})`)(function* (
51 request?: Omit<I, "spaceId">,
52 ) {
53 return yield* op({
54 ...request,
55 spaceId: yield* spaceId,
56 } as I);
57 });
58 });
59 });