{"id":17793393,"url":"https://github.com/jpb06/effect-cloudflare-r2-layer","last_synced_at":"2026-05-18T09:05:53.196Z","repository":{"id":257964572,"uuid":"863111185","full_name":"jpb06/effect-cloudflare-r2-layer","owner":"jpb06","description":"An effect layer to interact with Cloudflare R2 storage service","archived":false,"fork":false,"pushed_at":"2026-05-14T04:42:12.000Z","size":1884,"stargazers_count":8,"open_issues_count":7,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-14T06:40:56.384Z","etag":null,"topics":["cloudflare","cloudflare-r2","effect-ts"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jpb06.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-09-25T18:23:00.000Z","updated_at":"2026-05-06T12:19:14.000Z","dependencies_parsed_at":"2025-05-04T08:27:33.822Z","dependency_job_id":"f66460d5-0729-4036-b88b-737d827c75fa","html_url":"https://github.com/jpb06/effect-cloudflare-r2-layer","commit_stats":null,"previous_names":["jpb06/effect-cloudflare-r2-layer"],"tags_count":130,"template":false,"template_full_name":null,"purl":"pkg:github/jpb06/effect-cloudflare-r2-layer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-cloudflare-r2-layer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-cloudflare-r2-layer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-cloudflare-r2-layer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-cloudflare-r2-layer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpb06","download_url":"https://codeload.github.com/jpb06/effect-cloudflare-r2-layer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-cloudflare-r2-layer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33128043,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T18:38:32.183Z","status":"online","status_checked_at":"2026-05-17T02:00:05.366Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudflare","cloudflare-r2","effect-ts"],"created_at":"2024-10-27T11:08:23.049Z","updated_at":"2026-05-18T09:05:53.161Z","avatar_url":"https://github.com/jpb06.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# effect-cloudflare-r2-layer\n\n[![Open in Visual Studio Code](https://img.shields.io/static/v1?logo=visualstudiocode\u0026label=\u0026message=Open%20in%20Visual%20Studio%20Code\u0026labelColor=2c2c32\u0026color=007acc\u0026logoColor=007acc)](https://github.dev/jpb06/effect-cloudflare-r2-layer)\n![Last commit](https://img.shields.io/github/last-commit/jpb06/effect-cloudflare-r2-layer?logo=git)\n![npm downloads](https://img.shields.io/npm/dw/effect-cloudflare-r2-layer?logo=npm\u0026logoColor=red\u0026label=npm%20downloads)\n![npm bundle size](https://img.shields.io/bundlephobia/min/effect-cloudflare-r2-layer)\n\nAn effect layer to interact with Cloudware R2 storage service.\n\n\u003c!-- readme-package-icons start --\u003e\n\n\u003cp align=\"left\"\u003e\u003ca href=\"https://docs.github.com/en/actions\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/GithubActions-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://www.typescriptlang.org/docs/\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/TypeScript.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://nodejs.org/en/docs/\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/NodeJS-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://bun.sh/docs\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Bun-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://aws.amazon.com/developer/language/javascript/\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/AWS-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://biomejs.dev/guides/getting-started/\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Biome-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://github.com/motdotla/dotenv#readme\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Dotenv-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://vitest.dev/guide/\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Vitest-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://www.effect.website/docs/quickstart\" target=\"_blank\"\u003e\u003cimg height=\"50\" width=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Effect-Dark.svg\" /\u003e\u003c/a\u003e\u003c/p\u003e\n\n\u003c!-- readme-package-icons end --\u003e\n\n## ⚡ Quick start\n\n### 🔶 Install\n\n```bash\nnpm i effect-cloudflare-r2-layer\n# or\npnpm i effect-cloudflare-r2-layer\n# or\nbun i effect-cloudflare-r2-layer\n```\n\n### 🔶 Use the layer\n\n```typescript\nimport { FetchHttpClient } from '@effect/platform';\nimport { Effect, Layer, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\nconst task = pipe(\n  FileStorageLayer.readAsText('my-bucket', 'some-file.txt'),\n  Effect.scoped,\n  Effect.provide(\n    Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)\n  )\n);\n\n/* task is of type\n\n  Effect.Effect\u003c\n    string, \n    ConfigError | HttpClientError | FileStorageError, \n    never\n  \u003e\n*/\n```\n\n## ⚡ Env variables\n\nThe layer requires the following env variables:\n\n```env\nCLOUDFLARE_ACCOUNT_ID=\"\"\nR2_DOCUMENTS_ACCESS_KEY_ID=\"\"\nR2_DOCUMENTS_SECRET_ACCESS_KEY=\"\"\n```\n\n## ⚡ API\n\n| function                               | description                                                                               |\n| -------------------------------------- | ----------------------------------------------------------------------------------------- |\n| [`createBucket`](#-createbucket)       | Create a bucket                                                                           |\n| [`bucketInfos`](#-bucketinfos)         | Get bucket infos                                                                          |\n| [`uploadFile`](#-uploadfile)           | Adds a file to the specified bucket                                                       |\n| [`deleteFile`](#-deleteFile)           | Removes a file from the specified bucket                                                  |\n| [`getFileUrl`](#-getfileurl)           | Gets a pre-signed url to fetch a ressource by its `filename` from the specified `bucket`. |\n| [`readAsJson`](#-readasjson)           | Fetches a file, expecting a content extending `Record\u003cstring, unknown\u003e`.                  |\n| [`readAsText`](#-readastext)           | Fetches a file as a string.                                                               |\n| [`readAsRawBinary`](#-readasrawbinary) | Fetches a file as raw binary (ArrayBuffer).                                               |\n| [`fileExists`](#-fileexists)           | Checks if a file exists in a bucket                                                       |\n\n### 🔶 `createBucket`\n\n```typescript\ntype createBucket = (\n  input: CreateBucketCommandInput\n) =\u003e Effect.Effect\u003c\n  CreateBucketCommandOutput,\n  FileStorageError | ConfigError,\n  FileStorage\n\u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { Effect, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\nconst task = pipe(\n  Effect.gen(function* () {\n    const result = yield* FileStorageLayer.createBucket({\n      Bucket: 'test',\n      CreateBucketConfiguration: {\n        Bucket: {\n          Type: 'Directory',\n          DataRedundancy: 'SingleAvailabilityZone',\n        },\n      },\n    });\n\n    // ...\n  }),\n  Effect.provide(CloudflareR2StorageLayerLive)\n);\n```\n\n### 🔶 `bucketInfos`\n\n```typescript\ntype BucketInfosInput\u003cTBucket extends string\u003e = {\n  Bucket: TBucket;\n  ExpectedBucketOwner?: string;\n};\n\ntype BucketInfosResult = {\n  region?: string;\n};\n\ntype bucketInfos = \u003cTBucket extends string\u003e(\n  input: BucketInfosInput\u003cTBucket\u003e\n) =\u003e Effect.Effect\u003c\n  BucketInfosResult,\n  ConfigError | FileStorageError | BucketNotFoundError,\n  FileStorage\n\u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { Effect, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Buckets = 'assets' | 'config';\n\nconst task = pipe(\n  Effect.gen(function* () {\n    const result = yield* FileStorageLayer.bucketInfos\u003cBuckets\u003e({\n      Bucket: 'assets',\n    });\n\n    // ...\n  }),\n  Effect.provide(CloudflareR2StorageLayerLive)\n);\n```\n\n### 🔶 `uploadFile`\n\nAdds a file to the specified bucket.\n\n```typescript\ninterface UploadFileInput\u003cTBucket extends string\u003e {\n  bucketName: TBucket;\n  key: string;\n  data: Buffer;\n  contentType: string | undefined;\n}\n\ntype uploadFile = \u003cTBucket extends string\u003e(\n  input: UploadFileInput\u003cTBucket\u003e\n) =\u003e Effect.Effect\u003c\n  PutObjectCommandOutput,\n  FileStorageError | ConfigError,\n  FileStorage\n\u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { FileSystem } from '@effect/platform/FileSystem';\nimport { NodeFileSystem } from '@effect/platform-node';\nimport { Effect, Layer, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Buckets = 'assets' | 'config';\nconst fileName = 'yolo.jpg';\nconst filePath = './assets/yolo.jpg';\n\nconst task = pipe(\n  Effect.gen(function* () {\n    const fs = yield* FileSystem;\n    const fileData = yield* fs.readFile(filePath);\n\n    yield* FileStorageLayer.uploadFile\u003cBuckets\u003e({\n      bucketName: 'assets',\n      documentKey: fileName,\n      data: Buffer.from(fileData),\n      contentType: 'image/jpeg',\n    });\n\n    // ...\n  }),\n  Effect.provide(\n    Layer.mergeAll(CloudflareR2StorageLayerLive, NodeFileSystem.layer)\n  )\n);\n```\n\n### 🔶 `deleteFile`\n\nRemoves a file from the specified bucket.\n\n```typescript\ninterface DeleteFileInput\u003cTBucket extends string\u003e {\n  bucketName: TBucket;\n  key: string;\n}\n\ntype deleteFile = \u003cTBucket extends string\u003e(\n  input: DeleteFileInput\u003cTBucket\u003e\n) =\u003e Effect.Effect\u003c\n  DeleteObjectCommandOutput,\n  FileStorageError | ConfigError,\n  FileStorage\n\u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { Effect, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Buckets = 'assets' | 'config';\nconst fileName = 'yolo.jpg';\nconst filePath = './assets/yolo.jpg';\n\nconst task = pipe(\n  Effect.gen(function* () {\n    yield* FileStorageLayer.deleteFile\u003cBuckets\u003e({\n      bucketName: 'assets',\n      documentKey: fileName,\n    });\n\n    // ...\n  }),\n  Effect.provide(CloudflareR2StorageLayerLive);\n);\n```\n\n### 🔶 `getFileUrl`\n\nGets a pre-signed url to fetch a ressource by its `filename` from the specified `bucket`.\n\n```typescript\ntype getFileUrl = \u003cTBucket extends string\u003e(\n  bucket: TBucket\n  fileName: string,\n) =\u003e Effect.Effect\u003c\n  string,\n  FileStorageError | ConfigError,\n  FileStorage\n\u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { Effect, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Buckets = 'assets' | 'config';\nconst filename = 'yolo.jpg';\n\nconst task = pipe(\n  Effect.gen(function* () {\n    const url = yield* FileStorageLayer.getFileUrl\u003cBuckets\u003e('assets', filename);\n\n    // ...\n  }),\n  Effect.provide(CloudflareR2StorageLayerLive);\n);\n```\n\n### 🔶 `readAsJson`\n\nFetches a file, expecting a content extending `Record\u003cstring, unknown\u003e`.\n\n```typescript\ntype readAsJson = \u003c\n  TBucket extends string,\n  TShape extends Record\u003cstring, unknown\u003e\n\u003e(\n  bucket: TBucket,\n  fileName: string\n) =\u003e Effect.Effect\u003c\n  TShape,\n  HttpClientError | FileStorageError | ConfigError,\n  FileStorage | Scope | HttpClient\u003e\n\u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { FetchHttpClient } from '@effect/platform';\nimport { Effect, Layer, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Buckets = 'assets' | 'config';\n\ntype JsonData = {\n  cool: boolean;\n  yolo: string;\n};\n\nconst task = pipe(\n  pipe(\n    Effect.gen(function* () {\n      const json = yield* FileStorageLayer.readAsJson\u003cBuckets, JsonData\u003e(\n        'config',\n        'app-config.json'\n      );\n\n      // json is of type JsonData ...\n    }),\n    Effect.scoped,\n    Effect.provide(\n      Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)\n    )\n  )\n);\n```\n\n### 🔶 `readAsText`\n\nFetches a file as a string.\n\n```typescript\nreadAsText: \u003cTBucket extends string\u003e(\n  bucketName: TBucket,\n  documentKey: string\n) =\u003e\n  Effect.Effect\u003c\n    string,\n    ConfigError | HttpClientError | FileStorageError,\n    FileStorage | Scope | HttpClient\n  \u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { FetchHttpClient } from '@effect/platform';\nimport { Effect, Layer, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Buckets = 'assets' | 'config';\n\nconst task = pipe(\n  pipe(\n    Effect.gen(function* () {\n      const text = yield* FileStorageLayer.readAsText\u003cBuckets\u003e(\n        'assets',\n        'content.txt'\n      );\n\n      // ...\n    }),\n    Effect.scoped,\n    Effect.provide(\n      Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)\n    )\n  )\n);\n```\n\n### 🔶 `readAsRawBinary`\n\nFetches a file as raw binary.\n\n```typescript\nreadAsRawBinary: \u003cTBucket extends string\u003e(\n  bucketName: TBucket,\n  documentKey: string\n) =\u003e\n  Effect.Effect\u003c\n    ArrayBuffer,\n    ConfigError | HttpClientError | FileStorageError,\n    FileStorage | Scope | HttpClient\n  \u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { FetchHttpClient } from '@effect/platform';\nimport { FileSystem } from '@effect/platform/FileSystem';\nimport { Effect, Layer, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Buckets = 'assets' | 'config';\n\nconst task = pipe(\n  pipe(\n    Effect.gen(function* () {\n      const buffer = yield* FileStorageLayer.readAsRawBinary\u003cBuckets\u003e(\n        'assets',\n        'yolo.jpg'\n      );\n\n      const fs = yield* FileSystem;\n      const buffer = Buffer.from(data);\n      yield* fs.writeFile('./file.jpg', buffer);\n    }),\n    Effect.scoped,\n    Effect.provide(\n      Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)\n    )\n  )\n);\n```\n\n### 🔶 `fileExists`\n\n```typescript\ntype fileExists = \u003cTBucket extends string\u003e(\n  bucket: TBucket,\n  fileName: string\n) =\u003e Effect.Effect\u003cboolean, ConfigError | FileStorageError, FileStorage\u003e;\n```\n\n#### 🧿 Example\n\n```typescript\nimport { Effect, pipe } from 'effect';\nimport {\n  CloudflareR2StorageLayerLive,\n  FileStorageLayer,\n} from 'effect-cloudflare-r2-layer';\n\ntype Bucket = 'assets' | 'config';\n\nconst filePath = 'my-app/config.json';\n\nconst task = pipe(\n  Effect.gen(function* () {\n    const exists = yield* FileStorageLayer.fileExists\u003cBucket\u003e(\n      'config',\n      filePath\n    );\n\n    // ...\n  }),\n  Effect.provide(CloudflareR2StorageLayerLive)\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpb06%2Feffect-cloudflare-r2-layer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpb06%2Feffect-cloudflare-r2-layer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpb06%2Feffect-cloudflare-r2-layer/lists"}