import type { Credentials } from "../Credentials.ts"; import type { Region } from "@distilled.cloud/aws/Region"; import type * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import type { Scope } from "effect/Scope"; import { AlchemyContext } from "../../AlchemyContext.ts"; import * as Bundle from "../../Bundle/Bundle.ts"; import { Docker } from "../../Docker/Docker.ts"; import { Platform, type PlatformProps, type PlatformServices } from "../../Platform.ts"; import * as Provider from "../../Provider.ts"; import { Resource } from "../../Resource.ts"; import type { RuntimeContext } from "../../RuntimeContext.ts"; import { type HostRuntimeContext, type ServerHost } from "../../Server/Process.ts"; import { Stack } from "../../Stack.ts"; import { AWSEnvironment, type AccountID } from "../Environment.ts"; import type { PolicyStatement } from "../IAM/Policy.ts"; import type { Providers } from "../Providers.ts"; import type { RegionID } from "../Region.ts"; export type JobDefinitionName = string; /** Revision-qualified job definition ARN (`.../job-definition/{name}:{revision}`). */ export type JobDefinitionArn = `arn:aws:batch:${RegionID}:${AccountID}:job-definition/${JobDefinitionName}:${number}`; declare const JobDefinitionConfigError_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: "JobDefinitionConfigError"; } & Readonly; /** * Raised when the props don't select exactly one of the two forms: the * low-level container form (`image` + `executionRoleArn`) or the * Effect-native form (`main`). */ export declare class JobDefinitionConfigError extends JobDefinitionConfigError_base<{ readonly message: string; }> { } export interface JobDefinitionProps extends PlatformProps { /** * Name (family) of the job definition. If omitted, a unique name is * generated. Content changes register a new revision under the same name. */ jobDefinitionName?: string; /** * Container image the job runs (low-level form, e.g. * `public.ecr.aws/docker/library/busybox:latest`). Mutually exclusive * with `main`. */ image?: string; /** * Module entrypoint for an Effect-native run-to-completion job (typically * `import.meta.url` from an inline Effect program). Alchemy bundles the * program, builds a container image, pushes it to a managed ECR * repository, and provisions the job/execution IAM roles — mutually * exclusive with a caller-supplied `image`. */ main?: string; /** * Named export to load from `main`. * @default "default" */ handler?: string; /** * Command to run in the container (low-level form). Overridable * per-submission via `containerOverrides.command`. The Effect-native form * bakes its entrypoint into the image instead. */ command?: string[]; /** * Fargate vCPUs for the job. Must be a valid Fargate size (0.25, 0.5, 1, * 2, 4, 8, 16) compatible with `memory`. * @default 0.25 */ vcpus?: number; /** * Memory (MiB) for the job. Must be compatible with `vcpus` per the * Fargate size matrix. * @default 512 */ memory?: number; /** * Environment variables set in the job container. */ environment?: Record; /** * Additional environment variables for the Effect-native container. * Non-string values are JSON-encoded. (Capability bindings also inject * their variables here automatically.) */ env?: Record; /** * IAM role assumed by the job's application code. For the Effect-native * form (`main`), Alchemy provisions and manages this role automatically * (binding policy statements attach to it). */ jobRoleArn?: string; /** * Execution role used by ECS/Fargate to pull the image and write logs. * Required for the low-level form; provisioned automatically for the * Effect-native form. */ executionRoleArn?: string; /** * Compute platforms that can run this definition. Use `EC2` for unmanaged * ECS compute environments. * @default ["FARGATE"] */ platformCapabilities?: ("EC2" | "FARGATE")[]; /** * Whether the Fargate task ENI gets a public IP. Required for image pulls * from public registries when running in public subnets. * @default "ENABLED" */ assignPublicIp?: "ENABLED" | "DISABLED"; /** * Default parameter substitutions for `Ref::` placeholders in `command`. */ parameters?: Record; /** * Number of times a failed job is retried (1-10). * @default 1 */ retryAttempts?: number; /** * Job execution timeout (minimum 60 seconds), e.g. `"15 minutes"` or * `Duration.minutes(15)`. */ timeout?: Duration.Input; /** * Propagate job definition tags to the ECS task. * @default false */ propagateTags?: boolean; /** * Bundler configuration for the Effect-native entrypoint: rolldown * `input`/`output` overrides plus pure-annotation options (`pure`). * `effect`, `@effect/*`, `alchemy`, `@alchemy.run/*`, and * `@distilled.cloud/*` are annotated as pure by default so unused code * from those packages is tree-shaken; list additional packages via * `pure.packages`, or disable with `pure: false`. */ build?: Bundle.BundleConfig; /** * Docker image build for the Effect-native form: optional full * `dockerfile`. When omitted, Alchemy generates a Dockerfile for the * bundled `index.mjs`. */ docker?: { /** * Base image when Alchemy generates the Dockerfile. * @default public.ecr.aws/docker/library/bun:1 */ base?: string; /** Full Dockerfile content (replaces generated Dockerfile). */ dockerfile?: string; }; /** * Additional managed policy ARNs for the managed job role * (Effect-native form only). */ jobRoleManagedPolicyArns?: string[]; /** * User-defined tags to apply to the job definition. */ tags?: Record; } export interface JobDefinition extends Resource<"AWS.Batch.JobDefinition", JobDefinitionProps, { jobDefinitionName: JobDefinitionName; jobDefinitionArn: JobDefinitionArn; revision: number; status: string; tags: Record; /** The full URI of the built container image (Effect-native form only). */ imageUri: string | undefined; /** The managed ECR repository name (Effect-native form only). */ repositoryName: string | undefined; /** The managed ECR repository URI (Effect-native form only). */ repositoryUri: string | undefined; /** The ARN of the managed job role (Effect-native form only). */ jobRoleArn: string | undefined; /** The name of the managed job role (Effect-native form only). */ jobRoleName: string | undefined; /** The ARN of the execution role the job definition uses. */ executionRoleArn: string | undefined; /** The name of the managed execution role (Effect-native form only). */ executionRoleName: string | undefined; /** Content hash of the bundled program (Effect-native form only). */ codeHash: string | undefined; }, { /** Environment variables injected into the job container. */ env?: Record; /** IAM policy statements attached to the managed job role. */ policyStatements?: PolicyStatement[]; }, Providers> { } export type JobDefinitionServices = Credentials | Region | ServerHost | AWSEnvironment; /** * The shape an Effect-native job implementation returns: a single `run` * Effect that is the run-to-completion body of the job. The container exits * 0 when it succeeds (job `SUCCEEDED`) and 1 when it fails (job `FAILED`, * subject to the definition's `retryAttempts`). */ export type JobDefinitionShape = void | { run: Effect.Effect; }; export interface JobDefinitionRuntimeContext extends HostRuntimeContext { readonly Type: "AWS.Batch.JobDefinition"; } /** * An AWS Batch job definition for Fargate container jobs. Job definitions are * immutable revisions — changing the container configuration registers a new * revision under the same name (like ECS task definitions); destroying the * resource deregisters every active revision. * * `JobDefinition` is a Platform: alongside the low-level container form * (`image` + `executionRoleArn`), it supports Effect-native run-to-completion * implementations — an inline Effect program that Alchemy bundles, * containerizes as the job container's command, pushes to a managed ECR * repository, and registers, provisioning the job and execution roles * automatically. Capability bindings (e.g. S3 `GetObject`) attach IAM policy * statements to the managed job role and inject their environment variables * into the container. * * ### Creating Job Definitions * **Example:** Busybox echo job (low-level container form) * ```typescript * const jobDef = yield* Batch.JobDefinition("EchoJob", { * image: "public.ecr.aws/docker/library/busybox:latest", * command: ["echo", "hello from batch"], * executionRoleArn: executionRole.roleArn, * }); * ``` * * **Example:** Sized job with environment * ```typescript * const jobDef = yield* Batch.JobDefinition("EtlJob", { * image: image.imageUri, * vcpus: 1, * memory: 2048, * environment: { STAGE: "prod" }, * jobRoleArn: jobRole.roleArn, * executionRoleArn: executionRole.roleArn, * retryAttempts: 3, * timeout: "15 minutes", * }); * ``` * * ### Effect-Native Jobs * **Example:** Tagged class with an inline run-to-completion Effect * ```typescript * export default class Nightly extends Batch.JobDefinition()( * "Nightly", * { main: import.meta.url, vcpus: 1, memory: 2048 }, * Effect.gen(function* () { * const getObject = yield* AWS.S3.GetObject(bucket); * return { * run: Effect.gen(function* () { * const data = yield* getObject({ key: "input.csv" }); * yield* Effect.log("processed nightly batch"); * }), * }; * }), * ) {} * ``` * * **Example:** Eager inline job * ```typescript * export default Batch.JobDefinition( * "Reindex", * { main: import.meta.url }, * Effect.succeed({ * run: Effect.log("reindex complete"), * }), * ); * ``` * * **Example:** Plain external script (bundled as-is) * ```typescript * // ./job.ts runs top-level and exits; Alchemy bundles + containerizes it. * const jobDef = yield* Batch.JobDefinition("Script", { * main: path.join(import.meta.dirname, "job.ts"), * }); * ``` * * ### Bundling & Tree-shaking * `main` is bundled with rolldown at deploy time. Top-level calls in the * `effect`, `@effect/*`, `alchemy`, `@alchemy.run/*`, and * `@distilled.cloud/*` packages receive `#__PURE__` annotations by * default, so anything the job doesn't use from those packages is * tree-shaken out of the bundle. Any other package — including your own * app — is left untouched unless you list it explicitly. * * **Example:** Treat additional packages as pure * Pass package names (or picomatch globs) via `build.pure.packages` to * annotate them in addition to the defaults. * ```typescript * { * main: import.meta.url, * build: { * pure: { packages: ["my-lib", "@my-scope/*"] }, * }, * } * ``` * * Listing a package annotates calls whose result is bound (variable * initializers, exports) — safe anywhere. If a listed package also * declares `"sideEffects": false` (or `[]`) in its `package.json`, that * combination opts it into full annotation: top-level calls whose result * is discarded (e.g. `router.on("/path", handler)` registrations) are * also marked pure and deleted under minification when unused. Only list * a `sideEffects: false` package if its modules really are free of * meaningful top-level side effects. The `effect`, `alchemy`, and * `@distilled.cloud` defaults declare exactly that, on purpose — their * modules are designed to be fully tree-shakeable. * * **Example:** Disable pure annotations * ```typescript * { * main: import.meta.url, * build: { pure: false }, * } * ``` * * @resource */ export declare const JobDefinition: Platform; export declare const JobDefinitionProvider: () => import("effect/Layer").Layer, never, AWSEnvironment | AlchemyContext | Credentials | Docker | import("effect/FileSystem").FileSystem | import("effect/unstable/http/HttpClient").HttpClient | import("effect/Path").Path | Stack | import("../../Stage.ts").Stage>; export {}; //# sourceMappingURL=JobDefinition.d.ts.map