import * as r2 from "@distilled.cloud/cloudflare/r2"; import * as Provider from "../../Provider.ts"; import { Resource } from "../../Resource.ts"; import { CloudflareEnvironment } from "../CloudflareEnvironment.ts"; import type * as Cloudflare from "../Providers.ts"; import * as Zone from "../Zone/index.ts"; export declare const isBucket: (value: any) => value is Bucket; export type BucketName = string; export type BucketCustomDomainZone = Zone.Reference; export type BucketCustomDomain = { /** * Custom domain name to attach to the bucket. */ name: string; /** * Zone that contains the custom domain. If omitted, the zone is inferred * from `domain`. Pass a zone ID string, a hostname in the zone, or any object * with a `zoneId` attribute such as `Cloudflare.Zone.Zone`. */ zone?: BucketCustomDomainZone; /** * Whether public bucket access is enabled at this custom domain. * @default true */ enabled?: boolean; /** * Allowlist of TLS ciphers in BoringSSL format. */ ciphers?: string[]; /** * Minimum TLS version accepted by the custom domain. * @default "1.0" */ minTLS?: "1.0" | "1.1" | "1.2" | "1.3"; }; export type BucketLifecycleCondition = { type: "Age"; /** * Maximum age of an object, in seconds, before the rule's action applies. */ maxAge: number; } | { type: "Date"; /** * Absolute date (ISO 8601) at which the rule's action applies. */ date: string; }; export type BucketLifecycleRule = { /** * Unique identifier for the rule within the bucket. */ id: string; /** * Whether the rule is enabled. * @default true */ enabled?: boolean; /** * Object key prefix the rule applies to. Use `""` (or omit) to match all * objects in the bucket. * @default "" */ prefix?: string; /** * Abort incomplete multipart uploads after the configured age. */ abortMultipartUploadsTransition?: { condition?: { type: "Age"; maxAge: number; }; }; /** * Delete matching objects after the configured age or on a specific date. */ deleteObjectsTransition?: { condition?: BucketLifecycleCondition; }; /** * Transition matching objects to a different storage class. Cloudflare R2 * only supports transitioning to `InfrequentAccess` today. */ storageClassTransitions?: { condition: BucketLifecycleCondition; storageClass: "InfrequentAccess"; }[]; }; export type BucketCorsRule = { /** * Optional label for this rule, shown in the Cloudflare dashboard. Not * used to correlate rules across updates — the CORS configuration is * always replaced as a whole. */ id?: string; /** * HTTP methods browsers are allowed to use in cross-origin requests. */ allowedMethods: ("GET" | "PUT" | "POST" | "DELETE" | "HEAD")[]; /** * Origins allowed to make cross-origin requests, e.g. * `"https://example.com"`. Use `"*"` to allow any origin. */ allowedOrigins: string[]; /** * Request headers browsers are allowed to send, e.g. `"range"` for * range reads. If omitted, only simple headers are allowed. */ allowedHeaders?: string[]; /** * Response headers the browser is allowed to expose to the requesting * JavaScript, e.g. `"etag"` or `"content-range"`. */ exposeHeaders?: string[]; /** * How long (in seconds) browsers may cache CORS preflight responses. * Browsers may cap this at 2 hours or less, even if 86400 is specified. */ maxAgeSeconds?: number; }; export type BucketProps = { /** * Name of the bucket. If omitted, a unique name will be generated. * @default ${app}-${stage}-${id} */ name?: string; /** * Storage class for newly uploaded objects. * @default "Standard" */ storageClass?: Bucket.StorageClass; /** * Jurisdiction where objects in this bucket are guaranteed to be stored. * @default "default" */ jurisdiction?: Bucket.Jurisdiction; /** * Location hint for the bucket. */ locationHint?: Bucket.Location; /** * Custom domains to attach to the bucket. Pass an empty array (or omit) * to remove all custom domains. */ domains?: BucketCustomDomain[]; /** * Object lifecycle rules applied to the bucket. Pass an empty array (or * omit) to clear all lifecycle rules. See the Cloudflare R2 docs for * supported transitions. */ lifecycleRules?: BucketLifecycleRule[]; /** * CORS rules applied to the bucket, controlling which cross-origin * browser requests are allowed against the bucket's public or S3 API * endpoints. Pass an empty array (or omit) to remove the CORS * configuration. */ cors?: BucketCorsRule[]; /** * Allow alchemy to delete every object in the bucket when the bucket * itself is deleted. * * R2 refuses to delete a bucket that still has objects in it — that * refusal is the last line of defense for your data, so alchemy does not * bypass it by default: destroying a non-empty bucket fails with * `BucketNotEmpty` and both the bucket and its objects survive. Set this * to `true` for buckets whose contents are disposable (caches, previews, * test fixtures). * * `alchemy unsafe nuke` empties buckets regardless, since it is an * explicitly operator-confirmed account teardown. * * @default false */ forceDestroy?: boolean; }; export type Bucket = Resource<"Cloudflare.R2.Bucket", BucketProps, { bucketName: BucketName; storageClass: Bucket.StorageClass; jurisdiction: Bucket.Jurisdiction; location: Bucket.Location | undefined; accountId: string; domains: Bucket.CustomDomain[]; lifecycleRules: Bucket.LifecycleRule[]; cors: Bucket.CorsRule[]; }, never, Cloudflare.Providers>; /** * A Cloudflare R2 object storage bucket with S3-compatible API. * * R2 provides zero-egress-fee object storage. Create a bucket as a resource, * then bind it to a Worker to read and write objects at runtime. * ### Creating a Bucket * **Example:** Basic R2 bucket * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket"); * ``` * * **Example:** Bucket with location hint * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * locationHint: "wnam", * }); * ``` * * ### Binding to a Worker * **Example:** Reading and writing objects * ```typescript * const bucket = yield* Cloudflare.R2.ReadWriteBucket(MyBucket); * * // Write an object * yield* bucket.put("hello.txt", "Hello, World!"); * * // Read an object * const object = yield* bucket.get("hello.txt"); * if (object) { * const text = yield* object.text(); * } * ``` * * **Example:** Streaming upload with content length * ```typescript * const bucket = yield* Cloudflare.R2.ReadWriteBucket(MyBucket); * * yield* bucket.put("upload.bin", request.stream, { * contentLength: Number(request.headers["content-length"] ?? 0), * }); * ``` * * ### Custom Domains * * Attach one or more custom domains to serve bucket objects from a hostname * you control. The domain's zone must already exist in your Cloudflare * account; the zone is inferred from the hostname when omitted, or you can * pass a `Cloudflare.Zone.Zone` resource, a zone ID, or any hostname inside the * zone via the `zone` field. * * **Example:** Single custom domain * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * domains: [{ name: "assets.example.com" }], * }); * ``` * * **Example:** Multiple custom domains * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * domains: [ * { name: "assets.example.com" }, * { name: "static.example.com" }, * ], * }); * ``` * * **Example:** Disable a custom domain without removing it * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * domains: [{ name: "assets.example.com", enabled: false }], * }); * ``` * * **Example:** Custom domain with explicit zone and TLS settings * ```typescript * const zone = yield* Cloudflare.Zone.Zone("ExampleZone", { * name: "example.com", * }); * * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * domains: [ * { * name: "assets.example.com", * zone, * minTLS: "1.2", * }, * ], * }); * ``` * * ### Object Lifecycle Rules * * Configure lifecycle rules to automatically delete objects, abort * incomplete multipart uploads, or transition objects to InfrequentAccess * storage. Pass an empty array (or omit) to clear all rules. See the * [Cloudflare R2 docs](https://developers.cloudflare.com/r2/buckets/object-lifecycles/) * for details and limits (max 1000 rules per bucket). * * **Example:** Delete objects 30 days after upload * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * lifecycleRules: [ * { * id: "expire-old-objects", * deleteObjectsTransition: { * condition: { type: "Age", maxAge: 60 * 60 * 24 * 30 }, * }, * }, * ], * }); * ``` * * **Example:** Transition to InfrequentAccess after 60 days, delete after 365 * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * lifecycleRules: [ * { * id: "archive-then-delete", * prefix: "logs/", * storageClassTransitions: [ * { * condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 }, * storageClass: "InfrequentAccess", * }, * ], * deleteObjectsTransition: { * condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 }, * }, * }, * ], * }); * ``` * * **Example:** Abort incomplete multipart uploads after 7 days * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * lifecycleRules: [ * { * id: "abort-stale-uploads", * abortMultipartUploadsTransition: { * condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 }, * }, * }, * ], * }); * ``` * * ### CORS * * Configure CORS rules so browsers can make cross-origin requests against * the bucket's public (custom domain / r2.dev) or S3 API endpoints. Pass an * empty array (or omit) to remove the CORS configuration. See the * [Cloudflare R2 docs](https://developers.cloudflare.com/r2/buckets/cors/) * for details. * * **Example:** Allow cross-origin reads from any origin * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * cors: [ * { * allowedMethods: ["GET", "HEAD"], * allowedOrigins: ["*"], * }, * ], * }); * ``` * * **Example:** Browser range reads (e.g. PMTiles map tiles) * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * domains: [{ name: "tiles.example.com" }], * cors: [ * { * allowedMethods: ["GET", "HEAD"], * allowedOrigins: ["https://map.example.com"], * allowedHeaders: ["range", "if-match"], * exposeHeaders: ["etag", "content-range"], * maxAgeSeconds: 3600, * }, * ], * }); * ``` * * **Example:** Allow uploads from a web app * ```typescript * const bucket = yield* Cloudflare.R2.Bucket("MyBucket", { * cors: [ * { * allowedMethods: ["GET", "PUT", "POST"], * allowedOrigins: ["https://app.example.com"], * allowedHeaders: ["content-type"], * exposeHeaders: ["etag"], * }, * ], * }); * ``` * * ### Deleting a Bucket * * R2 refuses to delete a bucket that still has objects in it, and alchemy * does not bypass that refusal: destroying a non-empty bucket fails with * `BucketNotEmpty` and both the bucket and its objects survive. Opt into * emptying the bucket first with `forceDestroy` for buckets whose contents * are disposable. * * **Example:** Empty the bucket on destroy * ```typescript * const cache = yield* Cloudflare.R2.Bucket("Cache", { * forceDestroy: true, * }); * ``` * * **Example:** Keep the bucket even when the stack goes away * ```typescript * import * as RemovalPolicy from "alchemy/RemovalPolicy"; * * const uploads = yield* Cloudflare.R2.Bucket("Uploads").pipe( * RemovalPolicy.retain(), * ); * ``` * * @resource * @product R2 * @category Storage & Databases */ export declare const Bucket: import("../../Resource.ts").ResourceClass; export declare namespace Bucket { type StorageClass = "Standard" | "InfrequentAccess"; type Jurisdiction = "default" | "eu" | "fedramp"; type Location = "apac" | "eeur" | "enam" | "weur" | "wnam" | "oc"; type LifecycleRule = { id: string; enabled: boolean; prefix: string; abortMultipartUploadsTransition: { condition: { type: "Age"; maxAge: number; } | undefined; } | undefined; deleteObjectsTransition: { condition: BucketLifecycleCondition | undefined; } | undefined; storageClassTransitions: { condition: BucketLifecycleCondition; storageClass: "InfrequentAccess"; }[] | undefined; }; type CorsRule = { id: string | undefined; allowedMethods: ("GET" | "PUT" | "POST" | "DELETE" | "HEAD")[]; allowedOrigins: string[]; allowedHeaders: string[] | undefined; exposeHeaders: string[] | undefined; maxAgeSeconds: number | undefined; }; type CustomDomain = { domain: string; zoneId: string | undefined; enabled: boolean; ciphers: string[] | undefined; minTLS: "1.0" | "1.1" | "1.2" | "1.3" | undefined; status: { ownership: "pending" | "active" | "deactivated" | "blocked" | "error" | "unknown"; ssl: "initializing" | "pending" | "active" | "deactivated" | "error" | "unknown"; } | undefined; }; } export declare const ProviderLive: () => import("effect/Layer").Layer, never, CloudflareEnvironment | import("../../Stack.ts").Stack | import("../../Stage.ts").Stage | r2.CloudflareOpContext>; /** * Local (dev) provider — the bucket is purely virtual: a `dev:`-prefixed * bucket name keyed into the local workerd R2 simulator (data under * `.alchemy/local/r2`). `toRuntimeBinding` lowers an `r2_bucket` binding * whose bucket name is `dev:`-prefixed onto the local R2 service. R2 has no * opaque id — the name IS the identity — so the `dev:` marker rides on the * name (a `:` can never appear in a real R2 bucket name). * * Custom domains, lifecycle rules, and CORS are deploy-side concerns with * no local behavior; the local attributes report them empty. */ export declare const ProviderLocal: () => import("effect/Layer").Layer, never, CloudflareEnvironment>; export declare const BucketProvider: () => import("effect/Layer").Layer, never, import("../../AlchemyContext.ts").AlchemyContext | CloudflareEnvironment | import("../../Stack.ts").Stack | import("../../Stage.ts").Stage | r2.CloudflareOpContext>; //# sourceMappingURL=Bucket.d.ts.map