1import type * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type { RuntimeContext } from "../../RuntimeContext.ts";
4import type { Bucket } from "./Bucket.ts";
5import type { PresignError } from "./BucketTypes.ts";
6
7export interface PresignGetObjectRequest {
8 /**
9 * Key of the object to mint a presigned download URL for.
10 */
11 key: string;
12 /**
13 * Number of seconds the URL remains valid (at most 7 days).
14 * @default 900
15 */
16 expiresIn?: number;
17 /**
18 * `Content-Type` the object is served with, signed into the URL as a
19 * `response-content-type` override. The downloader sends nothing extra.
20 */
21 contentType?: string;
22 /**
23 * `Content-Disposition` the object is served with (e.g.
24 * `attachment; filename="report.pdf"`), signed into the URL as a
25 * `response-content-disposition` override.
26 */
27 contentDisposition?: string;
28}
29
30/**
31 * Mint presigned download (GET) URLs for objects in an R2 {@link Bucket}, so
32 * a browser or any HTTP client can read an object without credentials or a
33 * Worker in the request path.
34 *
35 * Presigning is a pure SigV4 computation against R2's S3-compatible API — no
36 * request is made. When deployed, the binding mints a scoped account API
37 * token (`Workers R2 Storage Read`) and derives R2 S3 credentials from it;
38 * the URLs point at `https://{accountId}.r2.cloudflarestorage.com`.
39 *
40 * Under `alchemy dev`, a locally-emulated bucket is served on the Worker's
41 * local S3 endpoint (`{worker url}/cdn-cgi/local/r2/s3`) and the minted URLs
42 * point there — the same code runs unchanged in both modes.
43 *
44 * ### Presigning Download URLs
45 * **Example:** Mint a presigned GET URL
46 * ```typescript
47 * const presignGet = yield* Cloudflare.R2.PresignGetObject(bucket);
48 * const url = yield* presignGet({ key: "reports/2026-09.pdf" });
49 * // hand `url` to a browser — it can GET the object without credentials
50 * ```
51 *
52 * **Example:** Force a download with a short-lived URL
53 * ```typescript
54 * const url = yield* presignGet({
55 * key: "reports/2026-09.pdf",
56 * expiresIn: 60,
57 * contentDisposition: 'attachment; filename="report.pdf"',
58 * });
59 * ```
60 *
61 * **Example:** Provide the implementation on a Worker
62 * ```typescript
63 * export default class Api extends Cloudflare.Worker<Api>()(
64 * "Api",
65 * { main: import.meta.url },
66 * Effect.gen(function* () {
67 * const presignGet = yield* Cloudflare.R2.PresignGetObject(Files);
68 * return {
69 * fetch: Effect.gen(function* () {
70 * const url = yield* presignGet({ key: "hello.txt" });
71 * return HttpServerResponse.redirect(url);
72 * }),
73 * };
74 * }).pipe(Effect.provide(Cloudflare.R2.PresignGetObjectToken)),
75 * ) {}
76 * ```
77 *
78 * @binding
79 * @product R2
80 * @category Storage & Databases
81 */
82export interface PresignGetObject extends Binding.Service<
83 PresignGetObject,
84 "Cloudflare.R2.PresignGetObject",
85 (
86 bucket: Bucket,
87 ) => Effect.Effect<
88 (
89 request: PresignGetObjectRequest,
90 ) => Effect.Effect<string, PresignError, RuntimeContext>
91 >
92> {}
93
94export const PresignGetObject = Binding.Service<PresignGetObject>(
95 "Cloudflare.R2.PresignGetObject",
96);