1/** @effect-diagnostics anyUnknownInErrorContext:off */
2
3import * as Floci from "@alchemy.run/floci";
4import type { FlociError } from "@alchemy.run/floci";
5import { Credentials } from "@distilled.cloud/aws/Credentials";
6import type { RegionName } from "@distilled.cloud/aws/Region";
7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";
9import * as Redacted from "effect/Redacted";
10import * as ProviderLayer from "../../Local/ProviderLayer.ts";
11import type { Platform } from "../../Platform.ts";
12import type { ResourceClassLike, ResourceLike } from "../../Resource.ts";
13import { DEFAULT_LOCAL_ENDPOINT, LOCAL_ACCOUNT_ID } from "../AuthProvider.ts";
14import * as Endpoint from "../Endpoint.ts";
15import { AWSEnvironment } from "../Environment.ts";
16import * as Region from "../Region.ts";
17import { provideProviderContext } from "./ProviderContext.ts";
18
19/**
20 * Fixed dummy account used for every floci-emulated resource. Attributes
21 * computed from it (ARNs, queue URLs) double as proof that no real AWS
22 * account was involved.
23 */
24export const FLOCI_ACCOUNT_ID = LOCAL_ACCOUNT_ID;
25
26/** Region every floci-emulated resource lives in. */
27export const FLOCI_REGION = "us-east-1";
28
29// Annotated (not inferred): the inferred union names distilled's Endpoint
30// through a non-portable relative path (TS2883), and consumers only ever
31// hand this to `provideProviderContext`, which takes `Layer<any, any, never>`.
32const makeFlociServices = (): Layer.Layer<any, FlociError, never> => {
33 const region = FLOCI_REGION as RegionName;
34 const resolved = {
35 accessKeyId: Redacted.make("test"),
36 secretAccessKey: Redacted.make("test"),
37 sessionToken: undefined,
38 region,
39 };
40 const credentials = Effect.succeed(resolved);
41 return Layer.mergeAll(
42 // Pin every distilled SDK call made by a wrapped lifecycle method to the
43 // emulator gateway with dummy credentials in the emulator's region.
44 Endpoint.of(DEFAULT_LOCAL_ENDPOINT),
45 Region.of(FLOCI_REGION),
46 Layer.succeed(Credentials, credentials),
47 // Providers read `AWSEnvironment.current` inside lifecycle operations to
48 // compute ARNs/attrs (accountId, region) — override it so computed
49 // identities carry the dummy account and the emulator endpoint.
50 Layer.succeed(
51 AWSEnvironment,
52 Effect.succeed({
53 accountId: FLOCI_ACCOUNT_ID,
54 region: FLOCI_REGION,
55 credentials,
56 endpoint: DEFAULT_LOCAL_ENDPOINT,
57 }),
58 ),
59 // Building the services guarantees the emulator is serving: reuses
60 // anything already listening on the endpoint, otherwise starts (or
61 // revives) the managed `alchemy-floci` container and waits for health.
62 Layer.effectDiscard(Floci.ensureFloci({ port: Floci.DEFAULT_FLOCI_PORT })),
63 );
64};
65
66let flociServicesLayer: ReturnType<typeof makeFlociServices> | undefined;
67
68/**
69 * The floci-scoped override context for local-mode AWS providers, as a
70 * **module-memoized layer reference** (see the note on
71 * [Local/ProviderLayer.ts](../../Local/ProviderLayer.ts)): every
72 * {@link flociDual} registration shares this one reference, so the stack
73 * build's MemoMap constructs it — and runs `ensureFloci()` — exactly once
74 * per stack build, and only when a local-mode provider is actually demanded.
75 */
76export const flociServices = () => (flociServicesLayer ??= makeFlociServices());
77
78/**
79 * Registers an AWS resource provider with both a **live** and a **local**
80 * (floci-emulated) implementation via `ProviderLayer.dual`. The local
81 * variant is the SAME live provider code with every lifecycle method
82 * endpoint-wrapped to the floci emulator ({@link flociServices}), so
83 * `alchemy dev` routes the resource to the emulator while `alchemy deploy`
84 * (and `Alchemy.remote()` in dev) keeps hitting the real cloud.
85 *
86 * @example
87 * ```ts
88 * // in Providers.ts, replacing `S3.BucketProvider(),`:
89 * flociDual(S3.Bucket, () => S3.BucketProvider()),
90 * ```
91 */
92export const flociDual = <R extends ResourceLike, ROut, E, RIn>(
93 cls:
94 | ResourceClassLike<R>
95 | Platform<R, any, any, any, any>
96 | { Type: R["Type"] },
97 live: () => Layer.Layer<ROut, E, RIn>,
98) =>
99 ProviderLayer.dual(cls, {
100 live,
101 local: () => provideProviderContext(live(), flociServices()),
102 // Registered as the resource's local data plane so deploy-time binding
103 // clients (Action bodies, plan-time `execute`) route their API calls to
104 // the emulator whenever the bound resource resolves to local mode.
105 dataPlane: flociServices,
106 });