t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/test.resources.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

1504 lines
47 KiB
TypeScript

import { Unowned } from "@/AdoptPolicy";
import { AlchemyContext } from "@/AlchemyContext.ts";
import { Artifacts } from "@/Artifacts";
import * as Binding from "@/Binding.ts";
import { isResolved } from "@/Diff.ts";
import * as ProviderLayer from "@/Local/ProviderLayer.ts";
import { Platform, type Main, type PlatformProps } from "@/Platform.ts";
import * as Provider from "@/Provider.ts";
import { LOCAL_ID_PREFIX, type ProviderMode } from "@/ProviderMode.ts";
import { Resource, type ResourceBinding } from "@/Resource";
import { unpackEnvValue } from "@/RuntimeContext.ts";
import {
createHostRuntimeContext,
type HostRuntimeContext,
type ServerHost,
} from "@/Server/Process.ts";
import { Stack } from "@/Stack";
import * as State from "@/State/index";
import { isUnknown } from "@/Util/unknown";
import { Data } from "effect";
import * as Config from "effect/Config";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Context from "effect/Context";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Redacted from "effect/Redacted";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
// Bucket
export type BucketProps = {
name?: string;
};
export interface Bucket extends Resource<
"Test.Bucket",
BucketProps,
{
name: string;
bucketArn: string;
}
> {}
export const Bucket = Resource<Bucket>("Test.Bucket");
const bucketProvider = () =>
Provider.succeed(Bucket, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ id, news, output }) {
if (!isResolved(news)) return undefined;
}),
reconcile: Effect.fn(function* ({ id, news = {}, output }) {
if (output !== undefined) return output;
return {
name: news.name ?? id,
bucketArn: `arn:test:bucket:us-east-1:123456789:${id}`,
};
}),
delete: Effect.fn(function* ({ output }) {
return;
}),
});
// Queue
export type Props = {
name?: string;
};
export interface Queue extends Resource<
"Test.Queue",
Props,
{
name: string;
queueUrl: string;
}
> {}
export const Queue = Resource<Queue>("Test.Queue");
export const queueProvider = () =>
Provider.succeed(Queue, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ id, news = {}, output }) {
if (!isResolved(news)) return undefined;
}),
reconcile: Effect.fn(function* ({ id, news = {} }) {
const name = news.name ?? id;
return {
name,
queueUrl: `https://test.queue.com/${name}`,
};
}),
delete: Effect.fn(function* ({ output }) {}),
});
export type FunctionProps = {
name?: string;
env?: Record<string, string>;
};
export interface Function extends Resource<
"Test.Function",
FunctionProps,
{
name: string;
env: Record<string, string>;
functionArn: string;
}
> {}
export const Function = Resource<Function>("Test.Function");
export const functionProvider = () =>
Provider.succeed(Function, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ id, news, output }) {
if (!isResolved(news)) return undefined;
}),
reconcile: Effect.fn(function* ({ id, news = {} }) {
return {
name: news.name ?? id,
env: news.env ?? {},
functionArn: `arn:aws:lambda:us-west-2:084828582823:function:${id}`,
};
}),
delete: Effect.fn(function* ({ output }) {}),
});
export type BindingTargetProps = {
name?: string;
string?: string;
replaceString?: string;
};
export interface BindingTarget extends Resource<
"Test.BindingTarget",
BindingTargetProps,
{
name: string;
string: string;
env: Record<string, string>;
replaceString: BindingTargetProps["replaceString"];
},
{
env?: Record<string, string>;
}
> {}
export const BindingTarget = Resource<BindingTarget>("Test.BindingTarget");
export const bindingTargetProvider = () =>
Provider.effect(
BindingTarget,
Effect.gen(function* () {
return {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ id, news = {}, olds = {}, newBindings }) {
if (!isResolved(news)) return undefined;
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (hooks?.diff && isResolved(newBindings)) {
yield* hooks.diff(id, newBindings as ResourceBinding[]);
}
const n = news as BindingTargetProps;
const o = olds as BindingTargetProps;
if (n.replaceString !== o.replaceString) {
return {
action: "replace",
};
}
if (n.name !== o.name || n.string !== o.string) {
return {
action: "update",
};
}
return undefined;
}),
precreate: Effect.fn(function* ({ id, news = {} }) {
return {
name: news.name ?? id,
string: news.string ?? id,
env: {},
replaceString: news.replaceString,
};
}),
reconcile: Effect.fn(function* ({ id, news = {}, olds, bindings }) {
// The hook routing tracks the engine's create-vs-update intent.
// `olds === undefined` covers greenfield AND replacement-create
// (engine resets olds when minting a new instance), which is
// exactly when the test wants `failOn("X", "create")` to fire.
// `output === undefined` would miss replacements with `precreate`
// because precreate populates `output` before reconcile runs.
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (olds === undefined) {
if (hooks?.create) {
yield* hooks.create(id, news as TestResourceProps);
}
} else {
if (hooks?.update) {
yield* hooks.update(id, news as TestResourceProps);
}
}
return {
name: news.name ?? id,
string: news.string ?? id,
env: Object.assign(
{},
...bindings.map(
(binding: any) => binding.env ?? binding.data?.env ?? {},
),
),
replaceString: news.replaceString,
};
}),
delete: Effect.fn(function* ({ id }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (hooks?.delete) {
yield* hooks.delete(id);
}
return;
}),
};
}),
);
export type DeletedBindingRegressionProps = {
name?: string;
};
export interface DeletedBindingRegressionTarget extends Resource<
"Test.DeletedBindingRegressionTarget",
DeletedBindingRegressionProps,
{
name: string;
env: Record<string, string>;
},
{
env?: Record<string, string>;
}
> {}
export const DeletedBindingRegressionTarget =
Resource<DeletedBindingRegressionTarget>(
"Test.DeletedBindingRegressionTarget",
);
export const deletedBindingRegressionProvider = () =>
Provider.succeed(DeletedBindingRegressionTarget, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* () {}),
precreate: Effect.fn(function* ({ id, news = {} }) {
return {
name: news.name ?? id,
env: {},
};
}),
reconcile: Effect.fn(function* ({ id, news = {}, bindings }) {
return {
name: news.name ?? id,
env: Object.assign(
{},
...bindings.map(
(binding: any) => binding.env ?? binding.data?.env ?? {},
),
),
};
}),
delete: Effect.fn(function* () {}),
});
export type ArtifactProbeProps = {
value: string;
};
export interface ArtifactProbe extends Resource<
"Test.ArtifactProbe",
ArtifactProbeProps,
{
value: string;
artifactValue: string | undefined;
}
> {}
export const ArtifactProbe = Resource<ArtifactProbe>("Test.ArtifactProbe");
export const artifactProbeProvider = () =>
Provider.succeed(ArtifactProbe, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ news, olds }) {
const next = news as ArtifactProbeProps;
const prev = olds as ArtifactProbeProps | undefined;
const artifacts = yield* Artifacts;
const previous = yield* artifacts.get<string>("memo");
if (
previous !== undefined &&
previous !== next.value &&
previous !== prev?.value
) {
return { action: "replace" as const };
}
yield* artifacts.set("memo", next.value);
return next.value !== prev?.value
? { action: "update" as const }
: undefined;
}),
reconcile: Effect.fn(function* ({ news }) {
const props = news as ArtifactProbeProps;
const artifacts = yield* Artifacts;
return {
value: props.value,
artifactValue: yield* artifacts.get<string>("memo"),
};
}),
delete: Effect.fn(function* () {}),
});
// TestResource
export type TestResourceProps = {
string?: string;
stringArray?: string[];
object?: {
string: string;
};
replaceString?: string;
redacted?: Redacted.Redacted<string>;
redactedArray?: Redacted.Redacted<string>[];
};
export interface TestResource extends Resource<
"Test.TestResource",
TestResourceProps,
{
string: string;
stringArray: string[];
stableString: string;
stableArray: string[];
replaceString: TestResourceProps["replaceString"];
redacted: Redacted.Redacted<string> | undefined;
redactedArray: Redacted.Redacted<string>[] | undefined;
}
> {}
export class TestResourceHooks extends Context.Service<
TestResourceHooks,
{
create?: (id: string, props: TestResourceProps) => Effect.Effect<void, any>;
update?: (id: string, props: TestResourceProps) => Effect.Effect<void, any>;
delete?: (id: string) => Effect.Effect<void, any>;
/**
* If provided, invoked from a binding-aware provider's `diff` with the
* exact `newBindings` array the engine handed it. Lets a test assert what
* the plan stage observes (e.g. that duplicates were collapsed by sid).
*/
diff?: (
id: string,
newBindings: ResourceBinding[],
) => Effect.Effect<void, any>;
/**
* If provided, the read hook is invoked for the resource's `read` lifecycle
* operation. Return:
* - attrs (any object) to simulate an existing cloud resource that is
* adoptable
* - `undefined` to simulate a resource that does not exist
* - fail with `OwnedBySomeoneElse` to reject adoption
*/
read?: (
id: string,
) => Effect.Effect<TestResource["Attributes"] | undefined, any>;
}
>()("TestResourceHooks") {}
export const TestResource = Resource<TestResource>("Test.TestResource");
export const testResourceProvider = () =>
Provider.effect(
TestResource,
Effect.gen(function* () {
return {
list: () => Effect.succeed([]),
read: Effect.fn(function* ({ id, output }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (hooks?.read) {
return (yield* hooks.read(id)) as any;
}
return output;
}),
diff: Effect.fn(function* ({ id, news = {}, olds = {} }) {
if (!isResolved(news)) return undefined;
const n = news as TestResourceProps;
const o = olds as TestResourceProps;
if (n.replaceString !== o.replaceString) {
return {
action: "replace",
};
}
const redactedValue = (
r: Redacted.Redacted<string> | undefined,
): string | undefined =>
r && Redacted.isRedacted(r) ? Redacted.value(r) : undefined;
const redactedArrayValues = (
arr: Redacted.Redacted<string>[] | undefined,
): string[] | undefined => arr?.map((r) => Redacted.value(r));
const oldRedactedArr = redactedArrayValues(o.redactedArray);
const newRedactedArr = redactedArrayValues(n.redactedArray);
return isUnknown(n.string) ||
isUnknown(n.stringArray) ||
n.string !== o.string ||
n.stringArray?.length !== o.stringArray?.length ||
!!n.stringArray !== !!o.stringArray ||
n.stringArray?.some(isUnknown) ||
n.stringArray?.some((s, i) => s !== o.stringArray?.[i]) ||
redactedValue(n.redacted) !== redactedValue(o.redacted) ||
oldRedactedArr?.length !== newRedactedArr?.length ||
!!oldRedactedArr !== !!newRedactedArr ||
newRedactedArr?.some((s, i) => s !== oldRedactedArr?.[i])
? {
action: "update",
stables: ["stableString", "stableArray"],
}
: undefined;
}),
reconcile: Effect.fn(function* ({ id, news = {}, olds }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
// Branch on `olds` (engine's create-vs-update intent), not
// `output` — replacements arrive with a precreate stub in
// `output` but `olds === undefined`.
if (olds === undefined) {
if (hooks?.create) {
yield* hooks.create(id, news);
}
} else {
if (hooks?.update) {
yield* hooks.update(id, news);
}
}
return {
string: news.string ?? id,
stringArray: news.stringArray ?? [],
stableString: id,
stableArray: [id],
replaceString: news.replaceString,
redacted: news.redacted,
redactedArray: news.redactedArray,
};
}),
delete: Effect.fn(function* ({ id }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (hooks?.delete) {
yield* hooks.delete(id);
}
return;
}),
};
}),
);
// StaticStablesResource - A test resource that has static stables on the provider
// This simulates resources like VPC, Subnet, etc. where certain properties (e.g., vpcId, subnetId)
// are always stable and defined on the provider itself, not returned dynamically by diff()
export type StaticStablesResourceProps = {
string?: string;
tags?: Record<string, string>;
replaceString?: string;
};
export interface StaticStablesResource extends Resource<
"Test.StaticStablesResource",
StaticStablesResourceProps,
{
string: string;
tags: Record<string, string>;
stableId: string;
stableArn: string;
replaceString: StaticStablesResourceProps["replaceString"];
}
> {}
export class StaticStablesResourceHooks extends Context.Service<
StaticStablesResourceHooks,
{
create?: (
id: string,
props: StaticStablesResourceProps,
) => Effect.Effect<void, any>;
update?: (
id: string,
props: StaticStablesResourceProps,
) => Effect.Effect<void, any>;
delete?: (id: string) => Effect.Effect<void, any>;
}
>()("StaticStablesResourceHooks") {}
export const StaticStablesResource = Resource<StaticStablesResource>(
"Test.StaticStablesResource",
);
export const staticStablesResourceProvider = () =>
Provider.succeed(StaticStablesResource, {
list: () => Effect.succeed([]),
// KEY DIFFERENCE: Static stables defined on the provider itself
// These are always stable regardless of what diff() returns
stables: ["stableId", "stableArn"],
diff: Effect.fn(function* ({ id, news = {}, olds = {} }) {
if (!isResolved(news)) return undefined;
const n = news as StaticStablesResourceProps;
const o = olds as StaticStablesResourceProps;
// Replace when replaceString changes
if (n.replaceString !== o.replaceString) {
return { action: "replace" };
}
// For string changes, return update action
if (n.string !== o.string) {
return { action: "update" };
}
// For tag-only changes, return undefined (no action)
// This simulates the VPC bug: tags changed, arePropsChanged returns true,
// but diff() returns undefined because provider doesn't explicitly handle tags
return undefined;
}),
reconcile: Effect.fn(function* ({ id, news = {}, olds, output }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(StaticStablesResourceHooks),
);
// Branch on `olds` (engine create vs update intent). Replacements
// pass `output` from the previous generation if any, but engine
// resets `olds` to `undefined` for the new instance.
if (olds === undefined) {
if (hooks?.create) {
yield* hooks.create(id, news);
}
return {
string: news.string ?? id,
tags: news.tags ?? {},
stableId: output?.stableId ?? `stable-${id}`,
stableArn:
output?.stableArn ??
(`arn:test:resource:us-east-1:123456789:${id}` as const),
replaceString: news.replaceString,
};
}
if (hooks?.update) {
yield* hooks.update(id, news);
}
return {
string: news.string ?? id,
tags: news.tags ?? {},
stableId: output?.stableId ?? `stable-${id}`,
stableArn:
output?.stableArn ??
(`arn:test:resource:us-east-1:123456789:${id}` as const),
replaceString: news.replaceString,
};
}),
delete: Effect.fn(function* ({ id, output }) {
yield* Effect.logDebug(output.string);
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(StaticStablesResourceHooks),
);
if (hooks?.delete) {
yield* hooks.delete(id);
}
return;
}),
});
// KindStablesResource - reproduces providers whose stable output attributes
// include a `kind` field, such as PlanetScale databases.
export type KindStablesResourceProps = {
value: string;
upstream?: KindStablesResource;
};
export interface KindStablesResource extends Resource<
"Test.KindStablesResource",
KindStablesResourceProps,
{
kind: "postgresql";
value: string;
upstreamKind: "postgresql" | undefined;
}
> {}
export const KindStablesResource = Resource<KindStablesResource>(
"Test.KindStablesResource",
);
export const kindStablesResourceProvider = () =>
Provider.succeed(KindStablesResource, {
list: () => Effect.succeed([]),
stables: ["kind"],
diff: Effect.fn(function* ({ news, olds }) {
if (!isResolved(news)) return undefined;
if (news.value !== olds?.value) {
return { action: "update" };
}
return undefined;
}),
reconcile: Effect.fn(function* ({ news }) {
const upstream = news.upstream as
| KindStablesResource["Attributes"]
| undefined;
return {
kind: "postgresql",
value: news.value,
upstreamKind: upstream?.kind,
};
}),
delete: Effect.fn(function* () {}),
});
// OverrideStablesResource — declares BOTH a provider-level `stables` list AND
// a `diff` that returns its own `stables` list that DISAGREES with it. Used to
// assert that a present `diff.stables` OVERRIDES `provider.stables` during plan
// (rather than being merged with it):
// - `providerStable` is only in `provider.stables` (omitted by `diff.stables`)
// - `diffStable` is only in `diff.stables` (omitted by `provider.stables`)
// - `sharedStable` is in both
// Under override semantics, on a `string` change `providerStable` must be
// treated as CHANGED (downstream re-plans) while `diffStable`/`sharedStable`
// stay stable. Under the old merge, `providerStable` would wrongly stay stable.
export type OverrideStablesResourceProps = {
string?: string;
};
export interface OverrideStablesResource extends Resource<
"Test.OverrideStablesResource",
OverrideStablesResourceProps,
{
string: string;
providerStable: string;
diffStable: string;
sharedStable: string;
}
> {}
export const OverrideStablesResource = Resource<OverrideStablesResource>(
"Test.OverrideStablesResource",
);
export const overrideStablesResourceProvider = () =>
Provider.succeed(OverrideStablesResource, {
list: () => Effect.succeed([]),
stables: ["providerStable", "sharedStable"],
diff: Effect.fn(function* ({ news = {}, olds = {} }) {
if (!isResolved(news)) return undefined;
const n = news as OverrideStablesResourceProps;
const o = olds as OverrideStablesResourceProps;
if (n.string !== o.string) {
return {
action: "update",
stables: ["diffStable", "sharedStable"],
} as const;
}
return undefined;
}),
reconcile: Effect.fn(function* ({ id, news = {} }) {
return {
string: news.string ?? id,
providerStable: `provider-${id}`,
diffStable: `diff-${id}`,
sharedStable: `shared-${id}`,
};
}),
delete: Effect.fn(function* () {}),
});
export type PhasedTargetProps = {
desired: string;
replaceKey?: string;
};
export interface PhasedTarget extends Resource<
"Test.PhasedTarget",
PhasedTargetProps,
{
stableId: string;
value: string;
env: Record<string, string>;
replaceKey: string | undefined;
},
{
env?: Record<string, string>;
}
> {}
export const PhasedTarget = Resource<PhasedTarget>("Test.PhasedTarget");
const phasedStableId = (replaceKey?: string) =>
`stable:${replaceKey ?? "default"}`;
const mergeBindingEnv = (bindings: Array<any>) =>
Object.assign(
{},
...bindings.map((binding) => binding.env ?? binding.data?.env ?? {}),
);
export const phasedTargetProvider = () =>
Provider.effect(
PhasedTarget,
Effect.gen(function* () {
return {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ news, olds }) {
if (!isResolved(news)) return undefined;
const n = news as PhasedTargetProps;
const o = olds as PhasedTargetProps;
if (n.replaceKey !== o.replaceKey) {
return { action: "replace" } as const;
}
if (n.desired !== o.desired) {
return { action: "update" } as const;
}
}),
precreate: Effect.fn(function* ({ news }) {
return {
stableId: phasedStableId(news.replaceKey),
value: `pre:${news.desired}`,
env: {},
replaceKey: news.replaceKey,
};
}),
reconcile: Effect.fn(function* ({ id, news, olds, bindings }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
// Branch on `olds` not `output`: replacement-create has
// precreate-populated `output` but engine-cleared `olds`.
if (olds === undefined) {
if (hooks?.create) {
yield* hooks.create(id, {
string: news.desired,
replaceString: news.replaceKey,
});
}
} else {
if (hooks?.update) {
yield* hooks.update(id, {
string: news.desired,
replaceString: news.replaceKey,
});
}
}
return {
stableId: phasedStableId(news.replaceKey),
value: news.desired,
env: mergeBindingEnv(bindings),
replaceKey: news.replaceKey,
};
}),
delete: Effect.fn(function* ({ id }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (hooks?.delete) {
yield* hooks.delete(id);
}
}),
};
}),
);
// NoPrecreateBindingTarget - like BindingTarget but without precreate,
// used to test cycle detection for resources that cannot break cycles.
export type NoPrecreateBindingTargetProps = {
string?: string;
};
export interface NoPrecreateBindingTarget extends Resource<
"Test.NoPrecreateBindingTarget",
NoPrecreateBindingTargetProps,
{
string: string;
env: Record<string, string>;
},
{
env?: Record<string, string>;
}
> {}
export const NoPrecreateBindingTarget = Resource<NoPrecreateBindingTarget>(
"Test.NoPrecreateBindingTarget",
);
export const noPrecreateBindingTargetProvider = () =>
Provider.succeed(NoPrecreateBindingTarget, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* () {}),
reconcile: Effect.fn(function* ({ id, news = {}, bindings }) {
return {
string: news.string ?? id,
env: Object.assign(
{},
...bindings.map(
(binding: any) => binding.env ?? binding.data?.env ?? {},
),
),
};
}),
delete: Effect.fn(function* () {}),
});
// DurationResource — exercises Duration round-tripping through state.
// Input Duration must arrive at reconcile as a real Duration object (so the
// resolver doesn't shred its prototype); output Duration must re-hydrate from
// state on a subsequent deploy as a real Duration object too.
export type DurationResourceProps = {
timeout: Duration.Duration;
};
export interface DurationResource extends Resource<
"Test.DurationResource",
DurationResourceProps,
{
/** Echoes the input Duration so we can assert what reconcile observed. */
observedTimeout: Duration.Duration;
/** Authoritative Duration the provider produces; persisted to state. */
computedTimeout: Duration.Duration;
}
> {}
export const DurationResource = Resource<DurationResource>(
"Test.DurationResource",
);
export const durationResourceProvider = () =>
Provider.succeed(DurationResource, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ news }) {
if (!isResolved(news)) return undefined;
return undefined;
}),
reconcile: Effect.fn(function* ({ news }) {
// If `news.timeout` was shredded by the resolver into a plain object,
// these calls would throw or produce nonsense. The test asserts the
// numeric output so a regression surfaces as a failed assertion.
const observed = news.timeout;
const computed = Duration.millis(Duration.toMillis(observed) + 1_000);
return {
observedTimeout: observed,
computedTimeout: computed,
};
}),
delete: Effect.fn(function* () {}),
});
// DeleteFirstResource — exercises `{ action: "replace", deleteFirst: true }`.
//
// Models a resource whose replacement cannot coexist with the original (a
// fixed physical name / singleton). When `replaceString` changes it asks the
// engine to tear the old generation down BEFORE creating the new one.
//
// Two test affordances:
// - create/update/delete route through `TestResourceHooks` so a test can
// record the order the engine invokes them in.
// - if a `CollisionRegistry` is in context, create fails when an instance
// with the same physical `name` is still live. Under create-first ordering
// a same-name replacement would collide here (reproducing the real Docker
// "network already exists" / no-op `volume create` bug); under delete-first
// it succeeds.
export class CollisionRegistry extends Context.Service<
CollisionRegistry,
{ readonly live: Set<string> }
>()("CollisionRegistry") {}
export class CollisionError extends Data.TaggedError("CollisionError")<{
name: string;
}> {}
export type DeleteFirstResourceProps = {
string?: string;
replaceString?: string;
name?: string;
};
export interface DeleteFirstResource extends Resource<
"Test.DeleteFirstResource",
DeleteFirstResourceProps,
{
name: string;
string: string;
replaceString: DeleteFirstResourceProps["replaceString"];
}
> {}
export const DeleteFirstResource = Resource<DeleteFirstResource>(
"Test.DeleteFirstResource",
);
export const deleteFirstResourceProvider = () =>
Provider.succeed(DeleteFirstResource, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ news = {}, olds = {} }) {
if (!isResolved(news)) return undefined;
const n = news as DeleteFirstResourceProps;
const o = olds as DeleteFirstResourceProps;
if (n.replaceString !== o.replaceString) {
return { action: "replace", deleteFirst: true } as const;
}
if (n.string !== o.string) {
return { action: "update" } as const;
}
return undefined;
}),
reconcile: Effect.fn(function* ({ id, news = {}, olds }) {
const name = news.name ?? id;
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
const registry = Option.getOrUndefined(
yield* Effect.serviceOption(CollisionRegistry),
);
// `olds === undefined` ⇒ create (greenfield OR replacement-create); the
// engine clears `olds` when minting the new replacement generation.
if (olds === undefined) {
if (registry?.live.has(name)) {
return yield* Effect.fail(new CollisionError({ name }));
}
registry?.live.add(name);
if (hooks?.create) {
yield* hooks.create(id, {
string: news.string,
replaceString: news.replaceString,
});
}
} else if (hooks?.update) {
yield* hooks.update(id, {
string: news.string,
replaceString: news.replaceString,
});
}
return {
name,
string: news.string ?? id,
replaceString: news.replaceString,
};
}),
delete: Effect.fn(function* ({ id, output }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
const registry = Option.getOrUndefined(
yield* Effect.serviceOption(CollisionRegistry),
);
registry?.live.delete(output.name);
if (hooks?.delete) {
yield* hooks.delete(id);
}
}),
});
// ── DriftResource — exercises `alchemy drift --repair`.
//
// Models a cloud with an inspectable, mutable backing store (`TestCloud`):
// `reconcile` upserts the resource into the cloud map, `read` observes it,
// `delete` removes it. Tests mutate the map out-of-band to simulate drift
// (or delete entries to simulate out-of-band deletion) and assert that
// `sync` converges the cloud back to the last-deployed desired state.
//
// The map stores deep copies — the in-memory state store keeps references,
// so aliasing the persisted `attr` would make out-of-band mutations
// invisible to drift detection.
export interface TestCloudService {
/** Live cloud state keyed by logical id. Mutate/delete to simulate drift. */
readonly resources: Map<string, Record<string, any>>;
/** Ids whose `read` result is branded {@link Unowned} (foreign tags). */
readonly unowned: Set<string>;
/** Lifecycle invocations, in order. Clear between phases to scope asserts. */
readonly calls: { op: "read" | "reconcile" | "delete"; id: string }[];
}
export class TestCloud extends Context.Service<TestCloud, TestCloudService>()(
"TestCloud",
) {}
export const makeTestCloud = (): TestCloudService => ({
resources: new Map(),
unowned: new Set(),
calls: [],
});
export type DriftResourceProps = {
value?: string;
tags?: Record<string, string>;
};
export interface DriftResource extends Resource<
"Test.DriftResource",
DriftResourceProps,
{
id: string;
value: string;
tags: Record<string, string>;
env: Record<string, string>;
},
{
env?: Record<string, string>;
}
> {}
export const DriftResource = Resource<DriftResource>("Test.DriftResource");
export const driftResourceProvider = () =>
Provider.effect(
DriftResource,
Effect.gen(function* () {
const cloudOf = Effect.serviceOption(TestCloud).pipe(
Effect.map(Option.getOrUndefined),
);
const copy = (attrs: Record<string, any>) =>
JSON.parse(JSON.stringify(attrs)) as DriftResource["Attributes"];
return {
list: () => Effect.succeed([]),
read: Effect.fn(function* ({ id, output }) {
const cloud = yield* cloudOf;
// Without a TestCloud in context the resource behaves like
// TestResource: read reflects the persisted output back.
if (!cloud) return output;
cloud.calls.push({ op: "read", id });
const live = cloud.resources.get(id);
if (live === undefined) return undefined;
const attrs = copy(live);
return cloud.unowned.has(id) ? Unowned(attrs) : attrs;
}),
reconcile: Effect.fn(function* ({ id, news = {}, olds, bindings }) {
const cloud = yield* cloudOf;
cloud?.calls.push({ op: "reconcile", id });
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (olds === undefined) {
if (hooks?.create) {
yield* hooks.create(id, { string: news.value });
}
} else if (hooks?.update) {
yield* hooks.update(id, { string: news.value });
}
const attrs = {
id,
value: news.value ?? id,
tags: news.tags ?? {},
env: Object.assign(
{},
...bindings.map(
(binding: any) => binding.env ?? binding.data?.env ?? {},
),
),
};
cloud?.resources.set(id, copy(attrs));
return attrs;
}),
delete: Effect.fn(function* ({ id }) {
const cloud = yield* cloudOf;
cloud?.calls.push({ op: "delete", id });
cloud?.resources.delete(id);
}),
};
}),
);
// AliasedWidget — a resource whose type was "renamed" from `Test.Widget` to
// `Test.Widgets.Widget`. The legacy name is carried as an alias so state
// persisted under the old type still resolves to this provider. Its provider
// is intentionally NOT part of `TestLayers` — alias tests provide it as a
// bare layer or wrapped in a `ProviderCollection` to exercise both lookup
// paths in isolation.
export interface AliasedWidget extends Resource<
"Test.Widgets.Widget",
{ name?: string },
{
name: string;
}
> {}
export const AliasedWidget = Resource<AliasedWidget>("Test.Widgets.Widget", {
aliases: ["Test.Widget"],
});
/** Logical IDs whose provider `delete` ran — proves deletion went through the provider. */
export const aliasedWidgetDeletes: string[] = [];
export const aliasedWidgetProvider = () =>
Provider.succeed(AliasedWidget, {
list: () => Effect.succeed([]),
reconcile: Effect.fn(function* ({ id, news }) {
return { name: news?.name ?? id };
}),
delete: Effect.fn(function* ({ id }) {
aliasedWidgetDeletes.push(id);
}),
});
// FqnProbe — echoes the identity the engine threads into each handler input
// (`id` and `fqn`) back out as attributes. Lets a test assert that the engine
// passes the resource's real fully-qualified name — namespace path + logical
// id — which differs from the bare logical `id` for namespaced resources.
export interface FqnProbe extends Resource<
"Test.FqnProbe",
{},
{
id: string;
fqn: string;
}
> {}
export const FqnProbe = Resource<FqnProbe>("Test.FqnProbe");
export const fqnProbeProvider = () =>
Provider.succeed(FqnProbe, {
list: () => Effect.succeed([]),
diff: Effect.fn(function* ({ news }) {
if (!isResolved(news)) return undefined;
return undefined;
}),
reconcile: Effect.fn(function* ({ id, fqn }) {
return { id, fqn };
}),
delete: Effect.fn(function* () {}),
});
/**
* Run `eff` as if under `alchemy dev`: overrides `AlchemyContext.dev` so
* plans and applies inside resolve the LOCAL provider mode by default —
* exactly what the `alchemy dev` command does for a whole run.
*/
export const inDev = <A, E, R>(
eff: Effect.Effect<A, E, R>,
): Effect.Effect<A, E, R | AlchemyContext> =>
AlchemyContext.pipe(
Effect.flatMap((ctx) =>
eff.pipe(Effect.provideService(AlchemyContext, { ...ctx, dev: true })),
),
);
// ModalResource — a resource registered via `ProviderLayer.dual` with
// distinct live and local implementations. Used by provider-mode tests to
// assert: mode stamping in state, replace-on-mode-switch, mode-correct
// deletes of old generations/orphans, and lazy non-default-variant builds.
//
// Both recorders are module-level (like `aliasedWidgetDeletes`) and tag
// every entry with the scratch stack name so concurrent tests in the same
// file can filter to their own activity.
export type ModalResourceProps = {
value?: string;
/**
* Changing this forces an ordinary (same-mode) replacement — used to
* assert that `providerMode` survives provider-diff replacements and
* that old generations are deleted with their own stamped mode.
*/
replaceValue?: string;
/**
* Changing this forces a `deleteFirst` replacement — used to assert that
* `deleteOldGenerations` (the pre-create teardown) resolves each old
* generation's provider by ITS stamped mode.
*/
deleteFirstValue?: string;
};
export interface ModalResource extends Resource<
"Test.ModalResource",
ModalResourceProps,
{
value: string;
/** Which provider variant reconciled this instance. */
runtime: ProviderMode;
/**
* Physical identity marker, mirroring real local providers: the local
* variant fabricates a `dev:`-prefixed id, the live variant a plain
* one. Legacy-row tests rely on this to exercise the marker-inferred
* mode fallback for unstamped rows.
*/
instance: string;
/**
* Local dev URL — returned by the LOCAL variant only, mirroring how a
* local Worker's attrs carry its dev-proxy URL. Apply announces it as
* a `ready at <url>` note on local-mode rows.
*/
url?: string;
}
> {}
export const ModalResource = Resource<ModalResource>("Test.ModalResource");
/** Variant layer constructions, in order. */
export const modalBuilds: { stack: string; mode: ProviderMode }[] = [];
/** Lifecycle calls dispatched to each variant, in order. */
export const modalCalls: {
stack: string;
mode: ProviderMode;
op: "reconcile" | "delete";
id: string;
}[] = [];
// Some plan-level test harnesses build `TestLayers()` without a `Stack`
// service in context, so the discriminator is resolved as an option with a
// fallback rather than required at layer build.
const modalStackName = Effect.serviceOption(Stack).pipe(
Effect.map(Option.match({ onNone: () => "unknown", onSome: (s) => s.name })),
);
const modalVariant = (mode: ProviderMode) =>
Provider.effect(
ModalResource,
Effect.gen(function* () {
modalBuilds.push({ stack: yield* modalStackName, mode });
return {
list: () => Effect.succeed([]),
read: Effect.fn(function* ({ output }) {
// Echo persisted attrs (the TestResource default): no cloud to
// observe, and a cold-start probe (output undefined) finds nothing.
return output;
}),
diff: Effect.fn(function* ({ news, olds }) {
if (!isResolved(news)) return undefined;
const n = news as ModalResourceProps;
const o = olds as ModalResourceProps | undefined;
if (n.deleteFirstValue !== o?.deleteFirstValue) {
return { action: "replace", deleteFirst: true } as const;
}
if (n.replaceValue !== o?.replaceValue) {
return { action: "replace" } as const;
}
return n.value !== o?.value
? ({ action: "update" } as const)
: ({ action: "noop" } as const);
}),
reconcile: Effect.fn(function* ({ id, news = {}, olds }) {
// Failure injection via the shared TestResourceHooks (same pattern
// as bindingTargetProvider): `olds === undefined` is the engine's
// create intent — greenfield or replacement-create.
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (olds === undefined) {
if (hooks?.create) {
yield* hooks.create(id, {
string: (news as ModalResourceProps).value,
});
}
} else if (hooks?.update) {
yield* hooks.update(id, {
string: (news as ModalResourceProps).value,
});
}
modalCalls.push({
stack: yield* modalStackName,
mode,
op: "reconcile",
id,
});
return {
value: (news as ModalResourceProps).value ?? id,
runtime: mode,
instance: mode === "local" ? `${LOCAL_ID_PREFIX}${id}` : id,
...(mode === "local" ? { url: "http://localhost:1337" } : {}),
};
}),
delete: Effect.fn(function* ({ id }) {
const hooks = Option.getOrUndefined(
yield* Effect.serviceOption(TestResourceHooks),
);
if (hooks?.delete) {
yield* hooks.delete(id);
}
modalCalls.push({
stack: yield* modalStackName,
mode,
op: "delete",
id,
});
}),
};
}),
);
export const modalResourceProvider = () =>
ProviderLayer.dual(ModalResource, {
live: () => modalVariant("live"),
local: () => modalVariant("local"),
dataPlane: () => modalLocalDataPlane,
liveDataPlane: () => modalLiveDataPlane,
});
/**
* Which data-plane override a deploy-time binding client ran under.
* The local/live layers below stamp this; ambient (no wrap) is `"ambient"`.
*/
export class DataPlaneTag extends Context.Service<
DataPlaneTag,
"local" | "live"
>()("Test.DataPlaneTag") {}
export const modalLocalDataPlane = Layer.succeed(DataPlaneTag, "local");
export const modalLiveDataPlane = Layer.succeed(DataPlaneTag, "live");
/**
* Deploy-time binding used to pin Binding client routing: the returned
* client reads {@link DataPlaneTag}, which is only in context when the wrap
* provided the matching plane layer closest.
*/
export interface ProbeBinding extends Binding.Service<
ProbeBinding,
"Test.ProbeBinding",
(
resource: ModalResource | readonly ModalResource[],
) => Effect.Effect<() => Effect.Effect<"local" | "live" | "ambient">>
> {}
export const ProbeBinding = Binding.Service<ProbeBinding>("Test.ProbeBinding");
export const ProbeBindingLive = Layer.succeed(
ProbeBinding,
Effect.fn(function* (_resource: ModalResource | readonly ModalResource[]) {
return () =>
Effect.serviceOption(DataPlaneTag).pipe(
Effect.map((opt) => (Option.isSome(opt) ? opt.value : "ambient")),
);
}),
);
// CapturedConfigHost - a Platform whose Init reads `Config.String("CAPTURED_MODE")`.
// Its provider's diff always returns `noop` without comparing `env`, so only
// the engine's comparison of Init-captured values can plan an update (#1831).
// `reconcile` echoes the delivered value as the `mode` attribute.
export interface CapturedConfigHostProps extends PlatformProps {
main?: string;
env?: Record<string, unknown>;
}
export interface CapturedConfigHost extends Resource<
"Test.CapturedConfigHost",
CapturedConfigHostProps,
{ mode: unknown }
> {}
export const CapturedConfigHost: Platform<
CapturedConfigHost,
ServerHost,
Main<ServerHost>,
HostRuntimeContext
> = Platform("Test.CapturedConfigHost", {
createRuntimeContext: createHostRuntimeContext("Test.CapturedConfigHost"),
});
export const capturedConfigHostProvider = () =>
Provider.succeed(CapturedConfigHost, {
list: () => Effect.succeed([]),
diff: () => Effect.succeed({ action: "noop" as const }),
reconcile: Effect.fn(function* ({ news }) {
const mode = unpackEnvValue(news.env?.CAPTURED_MODE as string);
return { mode: Redacted.isRedacted(mode) ? Redacted.value(mode) : mode };
}),
delete: Effect.fn(function* () {}),
});
/** Declare the host with `mode` served to its Init's `Config` reads. */
export const capturedConfigHost = (mode: string) =>
Effect.gen(function* () {
const ambient = yield* ConfigProvider.ConfigProvider;
return yield* CapturedConfigHost(
"Host",
{ main: "index.ts" },
Effect.gen(function* () {
const value = yield* Config.String("CAPTURED_MODE");
return { fetch: Effect.succeed(HttpServerResponse.text(value)) };
}),
).pipe(
Effect.provideService(
ConfigProvider.ConfigProvider,
ConfigProvider.orElse(
ConfigProvider.fromUnknown({ CAPTURED_MODE: mode }),
ambient,
),
),
);
});
// Layers
export const TestLayers = () =>
Layer.mergeAll(
bucketProvider(),
fqnProbeProvider(),
queueProvider(),
functionProvider(),
bindingTargetProvider(),
deletedBindingRegressionProvider(),
artifactProbeProvider(),
testResourceProvider(),
staticStablesResourceProvider(),
kindStablesResourceProvider(),
overrideStablesResourceProvider(),
phasedTargetProvider(),
noPrecreateBindingTargetProvider(),
durationResourceProvider(),
deleteFirstResourceProvider(),
driftResourceProvider(),
modalResourceProvider(),
capturedConfigHostProvider(),
ProbeBindingLive,
);
export const InMemoryTestLayers = () =>
Layer.mergeAll(TestLayers(), State.inMemoryState());
// ── Failure injection helpers ──────────────────────────────────────────────
//
// These helpers produce TestResourceHooks records that can be passed to the
// `hook(...)` test combinator to inject failures or defects into specific
// resource lifecycle methods. `failOn` produces typed failures, while
// `dieOn` and `throwOn` produce defects (uncaught/thrown errors).
export class ResourceFailure extends Data.TaggedError("ResourceFailure")<{
message: string;
}> {
constructor(message = "Failed to create") {
super({ message });
}
}
type LifecycleHook = "create" | "update" | "delete";
export type LifecycleHooks = {
create?: (id: string, props: TestResourceProps) => Effect.Effect<void, any>;
update?: (id: string, props: TestResourceProps) => Effect.Effect<void, any>;
delete?: (id: string) => Effect.Effect<void, any>;
};
export const failOn = (
resourceId: string,
hook: LifecycleHook,
): LifecycleHooks => ({
[hook]: (id: string) =>
id === resourceId
? Effect.fail(new ResourceFailure())
: Effect.succeed(undefined),
});
export const failOnMultiple = (
failures: Array<{ id: string; hook: LifecycleHook }>,
): LifecycleHooks => {
const idsFor = (hook: LifecycleHook) =>
failures.filter((f) => f.hook === hook).map((f) => f.id);
const createFailures = idsFor("create");
const updateFailures = idsFor("update");
const deleteFailures = idsFor("delete");
return {
create: (id: string) =>
createFailures.includes(id)
? Effect.fail(new ResourceFailure())
: Effect.succeed(undefined),
update: (id: string) =>
updateFailures.includes(id)
? Effect.fail(new ResourceFailure())
: Effect.succeed(undefined),
delete: (id: string) =>
deleteFailures.includes(id)
? Effect.fail(new ResourceFailure())
: Effect.succeed(undefined),
};
};
export const dieOn = (
resourceId: string,
hook: LifecycleHook,
message = `dieOn:${resourceId}:${hook}`,
): LifecycleHooks => ({
[hook]: (id: string) =>
id === resourceId
? Effect.die(new Error(message))
: Effect.succeed(undefined),
});
export const throwOn = (
resourceId: string,
hook: LifecycleHook,
message = `throwOn:${resourceId}:${hook}`,
): LifecycleHooks => ({
[hook]: (id: string) =>
Effect.sync(() => {
if (id === resourceId) {
throw new Error(message);
}
}),
});