/** @effect-diagnostics anyUnknownInErrorContext:off */ import * as Config from "effect/Config"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Scope from "effect/Scope"; import * as HttpClient from "effect/unstable/http/HttpClient"; import { AlchemyContext } from "../AlchemyContext.ts"; import type { Input } from "../Input.ts"; import * as RpcProviderProxy from "../Local/RpcProviderProxy.ts"; import * as RpcSpawner from "../Local/RpcSpawner.ts"; import * as Plan from "../Plan.ts"; import { type CompiledStack, Stack, type StackEffect, type StackServices } from "../Stack.ts"; import { Stage } from "../Stage.ts"; import * as State from "../State/index.ts"; /** * Configuration shared by every test in a file. Pass to `Test.make(...)`. */ export interface MakeOptions { /** Provider layer for the stack — e.g. `AWS.providers()`, `Cloudflare.providers()`. */ providers: Layer.Layer; /** State store for top-level `deploy(Stack)` / `destroy(Stack)`; defaults to {@link State.localState}. */ state?: Layer.Layer; /** Override `ALCHEMY_PROFILE`; otherwise resolved from env / .env. */ profile?: string; /** Default stage for deploy/destroy (default `"test"`). */ stage?: string; /** * Engine-level adoption policy for this test run. When `true`, resources * without prior state will be adopted from the cloud via `provider.read` * (matching the CLI's `--adopt` flag). Defaults to `false`. */ adopt?: boolean; /** * Run providers in local-dev mode (matching the CLI's `alchemy dev` flag). * When `true`, resources like Cloudflare Workers run locally via workerd * instead of being deployed to the cloud. When omitted, falls back to the * `ALCHEMY_DEV` environment variable (`"1"` / `"true"` enable it). * * {@link ALCHEMY_TEST_DEV} (`ALCHEMY_TEST_DEV=1`) overrides this — use it * to force an entire existing live suite through local providers without * editing each `Test.make({ dev: true })`. */ dev?: boolean; /** * Run local providers behind the RPC sidecar proxy, matching the process * topology of the real `alchemy dev` command: an {@link RpcProviderProxy} * is installed, so `RpcProvider.providerServicesEffect` layers (e.g. * Cloudflare's `localRuntimeServices()`) are EMPTY in the test process and * RPC-backed providers run their lifecycle in a spawned sidecar process. * * Defaults to the resolved `dev` flag — dev tests run the real `alchemy * dev` topology unless they opt out. In-process dev (`sidecar: false`) * masks missing main-process lifecycle dependencies (the class of bug * behind #1007, where D1's local migrations only failed under `alchemy * dev`); it remains available because it IS still a real production path — * a plain `alchemy deploy` deleting a `providerMode: "local"` state row * runs the local provider in-process — and because in-process runs are * easier to debug. * * Fully lazy: only the proxy facade is installed up front. The spawner * HTTP server starts — and a sidecar child process is forked — on the * first provider session request (a deploy/destroy building an RPC-backed * local provider); a dev file that never does starts no processes at all. * Once started, the sidecar lives for the whole test file (its own scope, * closed by the adapter's final cleanup hook), so `beforeAll(deploy(Stack))` * + per-test requests work the same way they do under a real `alchemy dev` * session. */ sidecar?: boolean; } /** * The RPC sidecar topology used by the real `alchemy dev` command, in one * process-local layer: an {@link RpcSpawner} HTTP server that forks sidecar * processes on demand, and an {@link RpcProviderProxy} pointed at it. */ export declare const sidecarProxy: (options: { profile?: string; }) => Layer.Layer; /** * Force every `Test.make` into (or out of) local-dev mode, regardless of * the file's `dev` option. Unset leaves the option / `ALCHEMY_DEV` fallback * in place. Accepts the usual truthy/falsey strings (`true`/`1`/`yes`/`on`, * `false`/`0`/`no`/`off`). */ export declare const ALCHEMY_TEST_DEV: Config.Config>; /** The `ALCHEMY_TEST_DEV` override, if the env var is set. */ export declare const alchemyTestDevOverride: () => Option.Option; /** Resolve the effective `dev` flag: `ALCHEMY_TEST_DEV`, then options, then `ALCHEMY_DEV`. */ export declare const resolveDev: (options: { dev?: boolean; }) => boolean; /** Resolve the effective `sidecar` flag: defaults to the resolved `dev` flag. */ export declare const resolveSidecar: (options: MakeOptions) => boolean; /** * The sidecar runtime handed to each adapter's `make(...)`. * * `provide` installs a lazy {@link RpcProviderProxy} facade into an effect. * Installing the facade is free: the spawner HTTP server only starts (and, * downstream of it, a sidecar child process is only forked) when a provider * actually requests a session — i.e. when a deploy/destroy builds an * RPC-backed local provider. A dev file that never does starts nothing. * * The spawner (and the sidecar children it forks) is a PROCESS-WIDE * SINGLETON shared by every test file, refcounted per handle: all files run * in one bun process, and a per-file sidecar means a per-file bun child that * imports the entire alchemy + distilled module graph — dozens of concurrent * files at hundreds of MB each OOMs the machine. Stack isolation is * preserved because each RPC session carries its own stack environment (see * `SESSION_ENV_PARAM` in Local/RpcServerEnvironment.ts) and the child builds * a provider context per stack. The singleton's scope closes when the LAST * handle closes; `Test.make` runs at collection time (before any test), so * the refcount cannot dip to zero while later files still need it. Adapters * run `close` from the same final cleanup hook that closes the shared scope. */ export interface SidecarHandle { readonly provide: (eff: Effect.Effect) => Effect.Effect; readonly close: Effect.Effect; } export declare const makeSidecarHandle: (options: MakeOptions) => SidecarHandle | undefined; export type TestEffect = StackEffect; /** * Build the per-test runtime and return a self-contained Effect. * * Mirrors {@link "../bin/alchemy.ts"} composition: ConfigProvider via * `loadConfigProvider` + `withProfileOverride`, an empty `AuthProviders` * registry that the user's `providers` layer populates, the platform layers, * and the configured state store. Adapters wrap this into runner-specific * thunks (`bun.test` -> `runPromise`, `it.live` -> as-is). * * When `scope` is provided, scoped resources (like the Cloudflare dev * sidecar) survive past this effect and are tied to the lifetime of the * provided scope instead. The runner is responsible for closing it. * * When `scope` is omitted, the effect runs with `Effect.scoped` and any * scoped resources are torn down as soon as it resolves. */ export declare const toEffect: (effect: TestEffect, options: MakeOptions, scope?: Scope.Scope, sidecar?: SidecarHandle) => Effect.Effect; /** Promise wrapper around {@link toEffect} for `bun.test`-style runners. */ export declare const run: (effect: TestEffect, options: MakeOptions, scope?: Scope.Scope, sidecar?: SidecarHandle) => Promise; /** * Wrap an effect so it runs with `options.providers` + a placeholder Stack + * Stage in scope. Used by `test.provider` so user code can call provider SDK * APIs (e.g. `DynamoDB.describeTable`) directly inside the test body. */ export declare const withProviders: (effect: Effect.Effect, options: MakeOptions, stackName: string) => Effect.Effect>; /** * Curried `deploy` for the test factory: bakes in the configured stage and * adds the telemetry layer the CLI uses, so `beforeAll(deploy(Stack))` works * the same way as `alchemy deploy`. * * `scope`, when supplied, is forwarded down so the dev sidecar (and other * scoped resources) lives until the caller closes it instead of dying as * soon as `deploy` resolves. The test harness uses this to keep workerd * alive across `beforeAll` → tests → `afterAll`. */ export declare const deploy: (options: MakeOptions, stack: TestEffect, Stage | AlchemyContext>, callOptions?: { stage?: string; scope?: Scope.Scope; }) => Effect.Effect, import("../Auth/AuthProvider.ts").AuthError | Config.ConfigError | import("../Auth/Demand.ts").CredentialsRequired | import("../Apply.ts").DestroyError | import("../Output.ts").InvalidReferenceError | import("../Output.ts").MissingSourceError | import("effect/PlatformError").PlatformError | State.StateStoreError, AlchemyContext | import("../Artifacts.ts").ArtifactStore | import("../index.ts").Cli | import("effect/FileSystem").FileSystem | import("effect/Path").Path | State.State>; export declare const destroy: (options: MakeOptions, stack: TestEffect, callOptions?: { stage?: string; scope?: Scope.Scope; }) => Effect.Effect; /** * In-test scratch stack handed to `test.provider(name, (stack) => ...)`. * * Each scratch stack owns a private state store that is shared between * successive `deploy`/`destroy` calls AND visible to the user's test body * (so `yield* State` / `state.get(...)` see the same store the deploys * mutated). This makes create / update / replace / delete paths exercisable * without polluting other tests in the same file. * * When the adapter can name the test file (the alchemy-test runner), the * store is DURABLE — rows live under `.alchemy/state/{file}-{test}/{stage}` * on disk. Durability is what makes an interrupted destroy recoverable: the * engine persists a `deleting` row before every `provider.delete` and only * drops it on success, so a run killed mid-delete (e.g. the runner's * teardown-abandonment after a test timeout) leaves resumable rows that the * NEXT run's leading `stack.destroy()` (or the `Effect.ensuring` teardown) * picks up and drains. An in-memory scratch dies with the process, silently * orphaning every cloud resource whose delete was still in flight. */ export interface ScratchStack { readonly name: string; /** The shared in-memory state Layer for this scratch. @internal */ readonly state: Layer.Layer; deploy(effect: Effect.Effect): Effect.Effect, any, Exclude>; /** * Build a plan against the scratch's shared state WITHOUT applying it. * * Use this to assert on the planned action for a resource (e.g. that a * downstream dependency stays `noop` when only an upstream resource * changes) without mutating the cloud. Plans run against whatever state * prior `deploy(...)` calls persisted. */ plan(effect: Effect.Effect): Effect.Effect, any, Exclude>; destroy(): Effect.Effect; } /** * Build a fresh `ScratchStack` for `test.provider`. * * With `file` (the registration-time test file, supplied by the * alchemy-test adapter): the store is the durable `.alchemy/state` local * store and the stack name is namespaced by file so interrupted destroys * leave resumable rows for the next run (see {@link ScratchStack}). * * Without `file` (bun/vitest adapters, which cannot name their file at * registration time): falls back to a private in-memory store — isolated, * but discarded with the process. */ export declare const scratchStack: (options: MakeOptions, name: string, file?: string) => ScratchStack; //# sourceMappingURL=Core.d.ts.map