import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import type * as Stream from "effect/Stream"; import type { Artifacts } from "./Artifacts.ts"; import type { ScopedPlanStatusSession } from "./Cli/Cli.ts"; import type { Diff } from "./Diff.ts"; import type { Input } from "./Input.ts"; import type { InstanceId } from "./InstanceId.ts"; import type { Platform } from "./Platform.ts"; import type { ProviderMode } from "./ProviderMode.ts"; import type { ResourceBinding, ResourceClass, ResourceClassLike, ResourceLike } from "./Resource.ts"; export interface Provider extends Effect.Effect, never, Provider> { asEffect: () => Effect.Effect, never, Provider>; [Symbol.iterator]: () => Effect.EffectIterator>; of: (service: Omit, "Type">) => ProviderService; } type LifecycleServices = InstanceId | Artifacts; export declare const Provider: (type: R["Type"]) => Provider; type BindingData = [Res] extends [ { Binding: infer B; } ] ? ResourceBinding[] : any[]; type Props = keyof Res["Props"] extends never ? Res["Props"] | undefined : Res["Props"]; export interface LogLine { timestamp: Date; message: string; } export interface LogsInput { since?: Date; limit?: number; } export interface ProviderService { /** * The version of the provider. * * @default 0 */ version?: number; /** * Legacy type names this provider also answers to. Copied from the * resource class's `aliases` option by {@link succeed}/{@link effect} so * state persisted under a pre-rename type (e.g. `"Cloudflare.Queue"` * before the `"Cloudflare.Queues.Queue"` rename) still resolves to this * provider via {@link tryFindProviderByType}. */ aliases?: readonly string[]; /** * The {@link ProviderMode} this service operates in. * * Set by `ProviderLayer.dual` registrations: the delegating service * registered in context carries the run's default mode, and each built * variant carries its concrete mode. Mode-agnostic providers (plain * {@link succeed}/{@link effect} registrations) leave it `undefined` — * their resources never persist a mode and never replace on a mode * switch. */ mode?: ProviderMode; /** * Lazily-built mode variants, present only for `ProviderLayer.dual` * registrations. Each effect builds (once, memoized, into the stack's * layer scope) the provider service for that mode — including any * mode-specific dependency layers — and returns it. The engine resolves * a concrete variant via {@link findProviderByType} with a mode so that * e.g. a local-mode state row is always deleted by the local provider, * even during a live deploy (and vice versa). */ modes?: { readonly [M in ProviderMode]: Effect.Effect>; }; /** * Account-wide teardown (`alchemy unsafe nuke`) behaviour. Providers whose * resources can't meaningfully be deleted opt out here so nuke doesn't * report an endless "deleted but still there" loop. `read`/import are * unaffected. */ nuke?: { /** * The resource is an account/zone **singleton setting** — always-present * configuration (e.g. Bot Management, Email Routing, a zone's SSL * settings) whose `delete` only *resets* it to defaults rather than * removing a discrete resource. Skipped by nuke, since `list` always * re-enumerates it and "deleting" it just resets config the operator * never created. */ singleton?: boolean; /** * The resource is skipped by nuke for any other reason — typically because * it can never actually be deleted (no delete API, like RealtimeKit Apps; * or a registration that is never released, like Registrar Domains). Unlike * {@link singleton}, these are ordinary multi-instance resources. */ skip?: boolean; /** * Provider IDs (picomatch globs, e.g. `"AWS.IAM.Role"` or `"AWS.EC2.*"`) * whose resources this type's **cloud-side teardown** consumes — they * must still exist while this type is deleting. `alchemy unsafe nuke` * topologically orders deletion so every resource of this type is fully * gone before any matching type starts deleting; if this type's deletes * fail, the matching types are held back rather than deleted out from * under an in-flight teardown. * * Example: SageMaker HyperPod deletes node ENIs by assuming the * instance group's execution role — deleting the role (or the VPC) * mid-teardown wedges the cluster in `Deleting` permanently, so * `AWS.SageMaker.Cluster` declares `dependsOn: ["AWS.IAM.Role", ...]`. * * The constraint only takes effect when resources of this type are * actually present in the nuke target set; declaration cycles collapse * into a single concurrent wave (with a logged warning) instead of * failing. */ dependsOn?: readonly string[]; }; /** * Enumerates every existing resource of this type in the ambient scope * (account / region / zone resolved from the environment services), and * returns the full {@link ProviderService} `Attributes` shape for each — * the same shape {@link read} produces, so each item is directly usable * with {@link delete} without a follow-up read. * * This powers account-wide operations such as `alchemy nuke`, which lists * everything and then deletes it. It takes no input and must paginate * exhaustively so the returned array is complete. * * Resources with no native enumeration API (account/zone singletons, * existence-only resources, sub-resources keyed entirely by a parent) * should return an empty array rather than throwing. */ list(): Effect.Effect; /** * Returns a stream of log lines for a deployed resource. * Used by `alchemy tail` to stream real-time logs. */ tail?(input: { id: string; /** * Fully-qualified name (namespace path + logical id, see `./FQN.ts`) — * globally unique, so providers can stamp ownership metadata on cloud objects. */ fqn: string; instanceId: string; props: Props; output: Res["Attributes"]; }): Stream.Stream; /** * Queries historical logs for a deployed resource. * Used by `alchemy logs` to fetch past log entries. */ logs?(input: { id: string; /** * Fully-qualified name (namespace path + logical id, see `./FQN.ts`) — * globally unique, so providers can stamp ownership metadata on cloud objects. */ fqn: string; instanceId: string; props: Props; output: Res["Attributes"]; options: LogsInput; }): Effect.Effect; read?(input: { id: string; /** * Fully-qualified name (namespace path + logical id, see `./FQN.ts`) — * globally unique, so providers can stamp ownership metadata on cloud objects. */ fqn: string; instanceId: string; olds: Props; output: Res["Attributes"] | undefined; }): Effect.Effect; /** * Properties that are always stable across any update. */ stables?: Extract[]; diff?(input: { id: string; /** * Fully-qualified name (namespace path + logical id, see `./FQN.ts`) — * globally unique, so providers can stamp ownership metadata on cloud objects. */ fqn: string; instanceId: string; olds: Props; news: Input>; oldBindings: BindingData; newBindings: Input>; output: Res["Attributes"] | undefined; }): Effect.Effect; precreate?(input: { id: string; /** * Fully-qualified name (namespace path + logical id, see `./FQN.ts`) — * globally unique, so providers can stamp ownership metadata on cloud objects. */ fqn: string; news: Props; instanceId: string; session: ScopedPlanStatusSession; bindings: BindingData; }): Effect.Effect; /** * Reconciles the desired state of a Resource with the live cloud state. * * This unified lifecycle method replaces the previous `create` and `update` * pair. The engine dispatches `reconcile` for both intents — initial * provisioning and subsequent updates — and providers must defensively * handle every combination of inputs: * * - `output === undefined` and `olds === undefined` — first reconciliation * for this logical resource. Treat as a create. Must remain idempotent * because state persistence can fail after a successful API call. * - `output !== undefined` and `olds === undefined` — engine adopted an * existing cloud resource (via {@link read}). The provider has never * written this resource through Alchemy before, so cannot rely on prior * props as a baseline. * - `output !== undefined` and `olds !== undefined` — standard update * path with a known prior state. * * Ownership has already been verified upstream — by the time `reconcile` * runs, the engine has confirmed (via `read` returning a non-`Unowned` * value, or by writing the resource itself) that mutation is safe. */ reconcile(input: { id: string; /** * Fully-qualified name (namespace path + logical id, see `./FQN.ts`) — * globally unique, so providers can stamp ownership metadata on cloud objects. */ fqn: string; instanceId: string; news: Props; olds: Props | undefined; output: Res["Attributes"] | undefined; session: ScopedPlanStatusSession; bindings: BindingData; }): Effect.Effect; delete(input: { id: string; /** * Fully-qualified name (namespace path + logical id, see `./FQN.ts`) — * globally unique, so providers can stamp ownership metadata on cloud objects. */ fqn: string; instanceId: string; olds: Props; output: Res["Attributes"]; session: ScopedPlanStatusSession; bindings: BindingData; /** * Set by account-wide teardown (`alchemy unsafe nuke`) to signal the * operator explicitly requested destructive deletion. Nuke enumerates * resources straight from the cloud, so `olds` carries Attributes rather * than the originally-deployed Props — destructive prerequisites that a * normal destroy gates behind a prop (e.g. emptying a non-empty S3 * bucket behind `forceDestroy`) may be performed when this is set. * Normal engine destroys never set it. */ force?: boolean; }): Effect.Effect; } /** * The provider shape accepted by {@link effect} / {@link succeed}: identical * to {@link ProviderService} except `list` is **optional**. Providers with no * native enumeration API (local/dev providers, account singletons, * sub-resources keyed entirely by a parent) simply omit it and get a safe * `() => Effect.succeed([])` default. * * Making `list` optional at the constructor (rather than on * {@link ProviderService} itself) keeps the engine's contract intact — every * registered provider still has a callable `list` — while removing the * type-inference trap where a missing required member silently defaults all * `*Req` type params to `never` and produces baffling `R is not assignable * to never` cascades at every other property. */ export type ProviderServiceInput = Omit, "list"> & Partial, "list">>; export declare const effect: (cls: ResourceClassLike | Platform, eff: Effect.Effect, never, Req>) => Layer.Layer, never, Exclude>; export declare const succeed: (cls: ResourceClass | Platform, service: ProviderServiceInput) => Layer.Layer, never, Exclude>; export interface ProviderCollectionLike { kind: "ProviderCollection"; } export interface ProviderCollectionShape extends Context.ServiceClass.Shape, ProviderCollectionLike { } export interface ProviderCollection extends Context.Service, ProviderCollectionLike { readonly key: Identifier; new (_: never): ProviderCollectionShape; } export declare const ProviderCollection: () => (id: ProviderId) => ProviderCollection; export interface ProviderCollectionService { kind: "ProviderCollection"; get(service: string): ProviderService | undefined; /** * Every provider in this collection keyed by its resource type * (e.g. `"Cloudflare.Worker"`). Used by account-wide operations such * as `alchemy unsafe nuke` to enumerate and filter providers — the * collection's closure-captured map would otherwise be unreachable. */ readonly providers: Record; } export declare const collection: | Platform>(resources: R[]) => Effect.Effect | Platform ? Provider : never>; /** * Resolve the concrete service for `mode` from a provider found in context. * * - `mode === undefined` → the service as registered (for dual * registrations that is the delegating service for the run's default * mode). * - `mode` given and the provider carries {@link ProviderService.modes} → * the (lazily built, memoized) variant for that mode. * - `mode` given but the provider is mode-agnostic → the service itself: * a single implementation satisfies every mode (hybrid), which is what * lets live-only resources (e.g. R2 buckets) participate in dev runs. */ export declare const providerForMode: (service: ProviderService, mode: ProviderMode | undefined) => Effect.Effect>; export declare const findProviderByType: { (resourceType: R["Type"], mode?: ProviderMode): Effect.Effect>; }; /** * Typed provider lookup by resource class (or {@link Platform}) value. Infers * `R` from the class so `provider.list()` / `provider.read(...)` return the * resource's `Attributes` shape — prefer this over {@link findProviderByType}, * which only takes the type string. */ export declare const findProvider: { (resource: ResourceClassLike | Platform, mode?: ProviderMode): Effect.Effect>; }; declare const MissingProviderError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "MissingProviderError"; } & Readonly; /** * A persisted state row references a resource type with no registered * provider — the type was removed from the program (or renamed without an * alias) while its state row still exists ("zombie" row). * * This is FATAL at plan time: the program and state fundamentally disagree, * and without the provider the row's physical resource cannot be deleted * anyway, so proceeding would only strand it silently. The plan dies with * this error; nothing is deployed or destroyed until the provider is * re-registered (or aliased), or the state row is cleared manually. */ export declare class MissingProviderError extends MissingProviderError_base<{ message: string; resourceType: string; fqn: string; }> { } /** Build the fatal plan-time error for a zombie state row. */ export declare const missingProviderError: (resourceType: string, fqn: string) => MissingProviderError; export declare const tryFindProviderByType: { (resourceType: R["Type"], mode?: ProviderMode): Effect.Effect>>; }; export {}; //# sourceMappingURL=Provider.d.ts.map