1.1.2 • Published 3 months ago

effect-cloudflare-r2-layer v1.1.2

Weekly downloads
-
License
-
Repository
-
Last release
3 months ago

effect-cloudflare-r2-layer

Open in Visual Studio Code Last commit npm downloads npm bundle size

An effect layer to interact with Cloudware R2 storage service.

⚡ Quick start

🔶 Install

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

🔶 Use the layer

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:

CLOUDFLARE_ACCOUNT_ID=""
R2_DOCUMENTS_ACCESS_KEY_ID=""
R2_DOCUMENTS_SECRET_ACCESS_KEY=""

⚡ API

functiondescription
createBucketCreate a bucket
bucketInfosGet bucket infos
uploadFileAdds a file to the specified bucket
deleteFileRemoves a file from the specified bucket
getFileUrlGets a pre-signed url to fetch a ressource by its filename from the specified bucket.
readAsJsonFetches a file, expecting a content extending Record<string, unknown>.
readAsTextFetches a file as a string.
readAsRawBinaryFetches a file as raw binary (ArrayBuffer).
fileExistsChecks if a file exists in a bucket

🔶 createBucket

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

🧿 Example

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

type BucketInfosInput<TBucket extends string> = {
  Bucket: TBucket;
  ExpectedBucketOwner?: string;
};

type BucketInfosResult = {
  region?: string;
};

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

🧿 Example

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<Buckets>({
      Bucket: 'assets',
    });

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

🔶 uploadFile

Adds a file to the specified bucket.

interface UploadFileInput<TBucket extends string> {
  bucketName: TBucket;
  key: string;
  data: Buffer;
  contentType: string | undefined;
}

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

🧿 Example

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<Buckets>({
      bucketName: 'assets',
      documentKey: fileName,
      data: fileData,
      contentType: 'image/jpeg',
    });

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

🔶 deleteFile

Removes a file from the specified bucket.

interface DeleteFileInput<TBucket extends string> {
  bucketName: TBucket;
  key: string;
}

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

🧿 Example

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<Buckets>({
      bucketName: 'assets',
      documentKey: fileName,
    });

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

🔶 getFileUrl

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

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

🧿 Example

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<Buckets>('assets', filename);

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

🔶 readAsJson

Fetches a file, expecting a content extending Record<string, unknown>.

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

🧿 Example

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<Buckets, JsonData>(
        '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.

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

🧿 Example

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<Buckets>(
        'assets',
        'content.txt'
      );

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

🔶 readAsRawBinary

Fetches a file as raw binary.

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

🧿 Example

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<Buckets>(
        '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)
    )
  )
);

🔶 fileExists

type fileExists = <TBucket extends string>(
  bucket: TBucket,
  fileName: string
) => Effect.Effect<boolean, ConfigError | FileStorageError, FileStorage>;

🧿 Example

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

type Bucket = 'assets' | 'config';

const filePath = 'my-app/config.json';

const task = pipe(
  Effect.gen(function* () {
    const exists = yield* FileStorageLayer.fileExists<Bucket>(
      'config',
      filePath
    );

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

3 months ago

1.1.1

4 months ago

1.0.63

4 months ago

1.1.0

4 months ago

1.0.62

4 months ago

1.0.61

4 months ago

1.0.60

4 months ago

1.0.59

4 months ago

1.0.58

4 months ago

1.0.57

4 months ago

1.0.56

4 months ago

1.0.55

5 months ago

1.0.54

5 months ago

1.0.48

5 months ago

1.0.47

5 months ago

1.0.49

5 months ago

1.0.51

5 months ago

1.0.50

5 months ago

1.0.53

5 months ago

1.0.52

5 months ago

1.0.44

5 months ago

1.0.43

6 months ago

1.0.42

6 months ago

1.0.41

6 months ago

1.0.46

5 months ago

1.0.38

6 months ago

1.0.37

6 months ago

1.0.36

6 months ago

1.0.35

6 months ago

1.0.34

6 months ago

1.0.33

7 months ago

1.0.32

7 months ago

1.0.31

7 months ago

1.0.30

7 months ago

1.0.29

7 months ago

1.0.26

8 months ago

1.0.28

8 months ago

1.0.27

8 months ago

1.0.25

8 months ago

1.0.23

8 months ago

1.0.22

8 months ago

1.0.21

8 months ago

1.0.20

8 months ago

1.0.19

8 months ago

1.0.18

8 months ago

1.0.17

8 months ago

1.0.16

8 months ago

1.0.15

8 months ago

1.0.14

8 months ago

1.0.13

8 months ago

1.0.12

9 months ago

1.0.11

9 months ago

1.0.10

9 months ago

1.0.9

9 months ago

1.0.8

9 months ago

1.0.7

9 months ago

1.0.6

9 months ago

1.0.5

9 months ago

1.0.4

9 months ago

1.0.3

9 months ago

1.0.2

9 months ago

1.0.1

9 months ago