1.1.2 • Published 4 months ago

effect-cloudflare-r2-layer v1.1.2

Weekly downloads
-
License
-
Repository
-
Last release
4 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

4 months ago

1.1.1

5 months ago

1.0.63

5 months ago

1.1.0

5 months ago

1.0.62

5 months ago

1.0.61

5 months ago

1.0.60

5 months ago

1.0.59

5 months ago

1.0.58

5 months ago

1.0.57

5 months ago

1.0.56

5 months ago

1.0.55

6 months ago

1.0.54

6 months ago

1.0.48

6 months ago

1.0.47

6 months ago

1.0.49

6 months ago

1.0.51

6 months ago

1.0.50

6 months ago

1.0.53

6 months ago

1.0.52

6 months ago

1.0.44

6 months ago

1.0.43

7 months ago

1.0.42

7 months ago

1.0.41

7 months ago

1.0.46

6 months ago

1.0.38

7 months ago

1.0.37

7 months ago

1.0.36

7 months ago

1.0.35

8 months ago

1.0.34

8 months ago

1.0.33

8 months ago

1.0.32

8 months ago

1.0.31

8 months ago

1.0.30

8 months ago

1.0.29

9 months ago

1.0.26

9 months ago

1.0.28

9 months ago

1.0.27

9 months ago

1.0.25

9 months ago

1.0.23

9 months ago

1.0.22

9 months ago

1.0.21

9 months ago

1.0.20

9 months ago

1.0.19

9 months ago

1.0.18

9 months ago

1.0.17

9 months ago

1.0.16

9 months ago

1.0.15

9 months ago

1.0.14

9 months ago

1.0.13

9 months ago

1.0.12

10 months ago

1.0.11

10 months ago

1.0.10

10 months ago

1.0.9

10 months ago

1.0.8

10 months ago

1.0.7

10 months ago

1.0.6

10 months ago

1.0.5

10 months ago

1.0.4

10 months ago

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

10 months ago