Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jpb06/effect-cloudflare-r2-layer

An effect layer to interact with Cloudflare R2 storage service
https://github.com/jpb06/effect-cloudflare-r2-layer

cloudflare cloudflare-r2 effect-ts

Last synced: 19 days ago
JSON representation

An effect layer to interact with Cloudflare R2 storage service

Awesome Lists containing this project

README

        

# effect-cloudflare-r2-layer

An effect layer to interact with Cloudware R2 storage service.

        

## ⚡ Quick start

### 🔶 Install

```bash
npm i effect-cloudflare-r2-layer
# or
pnpm i effect-cloudflare-r2-layer
# or
bun i effect-cloudflare-r2-layer
```

### 🔶 Use the layer

```typescript
import { FetchHttpClient } from '@effect/platform';
import { Effect, Layer, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';

const task = pipe(
FileStorageLayer.readAsText('my-bucket', 'some-file.txt'),
Effect.scoped,
Effect.provide(
Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)
)
);

/* task is of type

Effect.Effect<
string,
ConfigError | HttpClientError | FileStorageError,
never
>
*/
```

## ⚡ Env variables

The layer requires the following env variables:

```env
CLOUDFLARE_ACCOUNT_ID=""
R2_DOCUMENTS_ACCESS_KEY_ID=""
R2_DOCUMENTS_SECRET_ACCESS_KEY=""
```

## ⚡ API

| function | description |
| -------------------------------------- | ----------------------------------------------------------------------------------------- |
| [`createBucket`](#-createbucket) | Create a bucket |
| [`bucketInfos`](#-bucketinfos) | Get bucket infos |
| [`uploadFile`](#-uploadfile) | Adds a file to the specified bucket |
| [`deleteFile`](#-deleteFile) | Removes a file from the specified bucket |
| [`getFileUrl`](#-getfileurl) | Gets a pre-signed url to fetch a ressource by its `filename` from the specified `bucket`. |
| [`readAsJson`](#-readasjson) | Fetches a file, expecting a content extending `Record`. |
| [`readAsText`](#-readastext) | Fetches a file as a string. |
| [`readAsRawBinary`](#-readasrawbinary) | Fetches a file as raw binary (ArrayBuffer). |

### 🔶 `createBucket`

```typescript
type createBucket = (
input: CreateBucketCommandInput
) => Effect.Effect<
CreateBucketCommandOutput,
FileStorageError | ConfigError.ConfigError,
FileStorage
>;
```

#### 🧿 Example

```typescript
import { Effect, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';

const task = pipe(
Effect.gen(function* () {
const result = yield* FileStorageLayer.createBucket({
Bucket: 'test',
CreateBucketConfiguration: {
Bucket: {
Type: 'Directory',
DataRedundancy: 'SingleAvailabilityZone',
},
},
});

// ...
}),
Effect.provide(CloudflareR2StorageLayerLive)
);
```

### 🔶 `bucketInfos`

```typescript
type BucketInfosInput = {
Bucket: TBucket;
ExpectedBucketOwner?: string;
};

type BucketInfosResult = {
region?: string;
};

type bucketInfos = (
input: BucketInfosInput
) => Effect.Effect<
BucketInfosResult,
ConfigError | FileStorageError | BucketNotFoundError,
FileStorage
>;
```

#### 🧿 Example

```typescript
import { Effect, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';

type Buckets = 'assets' | 'config';

const task = pipe(
Effect.gen(function* () {
const result = yield* FileStorageLayer.bucketInfos({
Bucket: 'assets',
});

// ...
}),
Effect.provide(CloudflareR2StorageLayerLive)
);
```

### 🔶 `uploadFile`

Adds a file to the specified bucket.

```typescript
interface UploadFileInput {
bucketName: TBucket;
key: string;
data: Buffer;
contentType: string | undefined;
}

type uploadFile = (
input: UploadFileInput
) => Effect.Effect<
PutObjectCommandOutput,
FileStorageError | ConfigError.ConfigError,
FileStorage
>;
```

#### 🧿 Example

```typescript
import { Effect, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';
import { readFile } from 'fs-extra';

type Buckets = 'assets' | 'config';
const fileName = 'yolo.jpg';
const filePath = './assets/yolo.jpg';

const task = pipe(
Effect.gen(function* () {
const fileData = yield* Effect.tryPromise({
try: () => readFile(filePath),
catch: (e) => new FsError({ cause: e }),
});

yield* FileStorageLayer.uploadFile({
bucketName: 'assets',
documentKey: fileName,
data: fileData,
contentType: 'image/jpeg',
});

// ...
}),
Effect.provide(CloudflareR2StorageLayerLive);
);
```

### 🔶 `deleteFile`

Removes a file from the specified bucket.

```typescript
interface DeleteFileInput {
bucketName: TBucket;
key: string;
}

type deleteFile = (
input: DeleteFileInput
) => Effect.Effect<
DeleteObjectCommandOutput,
FileStorageError | ConfigError.ConfigError,
FileStorage
>;
```

#### 🧿 Example

```typescript
import { Effect, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';
import { readFile } from 'fs-extra';

type Buckets = 'assets' | 'config';
const fileName = 'yolo.jpg';
const filePath = './assets/yolo.jpg';

const task = pipe(
Effect.gen(function* () {
const fileData = yield* Effect.tryPromise({
try: () => readFile(filePath),
catch: (e) => new FsError({ cause: e }),
});

yield* FileStorageLayer.deleteFile({
bucketName: 'assets',
documentKey: fileName,
});

// ...
}),
Effect.provide(CloudflareR2StorageLayerLive);
);
```

### 🔶 `getFileUrl`

Gets a pre-signed url to fetch a ressource by its `filename` from the specified `bucket`.

```typescript
type getFileUrl = (
bucket: TBucket
fileName: string,
) => Effect.Effect<
string,
FileStorageError | ConfigError.ConfigError,
FileStorage
>;
```

#### 🧿 Example

```typescript
import { Effect, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';

type Buckets = 'assets' | 'config';
const filename = 'yolo.jpg';

const task = pipe(
Effect.gen(function* () {
const url = yield* FileStorageLayer.getFileUrl('assets', filename);

// ...
}),
Effect.provide(CloudflareR2StorageLayerLive);
);
```

### 🔶 `readAsJson`

Fetches a file, expecting a content extending `Record`.

```typescript
type readAsJson = <
TBucket extends string,
TShape extends Record
>(
bucket: TBucket,
fileName: string
) => Effect.Effect<
TShape,
HttpClientError | FileStorageError | ConfigError.ConfigError,
FileStorage | Scope | HttpClient
>;
```

#### 🧿 Example

```typescript
import { FetchHttpClient } from '@effect/platform';
import { Effect, Layer, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';

type Buckets = 'assets' | 'config';

type JsonData = {
cool: boolean;
yolo: string;
};

const task = pipe(
pipe(
Effect.gen(function* () {
const json = yield* FileStorageLayer.readAsJson(
'config',
'app-config.json'
);

// json is of type JsonData ...
}),
Effect.scoped,
Effect.provide(
Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)
)
)
);
```

### 🔶 `readAsText`

Fetches a file as a string.

```typescript
readAsText: (
bucketName: TBucket,
documentKey: string
) =>
Effect.Effect<
string,
ConfigError | HttpClientError | FileStorageError,
FileStorage | Scope | HttpClient
>;
```

#### 🧿 Example

```typescript
import { FetchHttpClient } from '@effect/platform';
import { Effect, Layer, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';

type Buckets = 'assets' | 'config';

const task = pipe(
pipe(
Effect.gen(function* () {
const text = yield* FileStorageLayer.readAsText(
'assets',
'content.txt'
);

// ...
}),
Effect.scoped,
Effect.provide(
Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)
)
)
);
```

### 🔶 `readAsRawBinary`

Fetches a file as raw binary.

```typescript
readAsRawBinary: (
bucketName: TBucket,
documentKey: string
) =>
Effect.Effect<
ArrayBuffer,
ConfigError | HttpClientError | FileStorageError,
FileStorage | Scope | HttpClient
>;
```

#### 🧿 Example

```typescript
import { FetchHttpClient } from '@effect/platform';
import { Effect, Layer, pipe } from 'effect';
import {
CloudflareR2StorageLayerLive,
FileStorageLayer,
} from 'effect-cloudflare-r2-layer';
import fs from 'fs-extra';
import { TaggedError } from 'effect/Data';

export class FsError extends TaggedError('FsError')<{
cause?: unknown;
}> {}

type Buckets = 'assets' | 'config';

const task = pipe(
pipe(
Effect.gen(function* () {
const buffer = yield* FileStorageLayer.readAsRawBinary(
'assets',
'yolo.jpg'
);

yield* Effect.tryPromise({
try: () =>
fs.writeFile('./file.jpg', Buffer.from(buffer), {
encoding: 'utf-8',
}),
catch: (e) => new FsError({ cause: e }),
});
}),
Effect.scoped,
Effect.provide(
Layer.mergeAll(CloudflareR2StorageLayerLive, FetchHttpClient.layer)
)
)
);
```