Skip to main content

Functional Deno IO

Deno IO methods as valid Task monads perfect to write great point-free software in JavaScript.

deno land deno version GitHub release GitHub licence

Usage

This example uses the Ramda library - for simplification - but you should be able to use any library that implements the Fantasy-land specifications.

import { compose, chain, curry } from "https://x.nest.land/ramda@0.27.0/source/index.js";
import Either from "https://deno.land/x/functional@v1.0.0/library/Either.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";
import { Buffer, File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import { close, writeAll, create } from "https://deno.land/x/functional_io@v0.3.2/fs.js";

const writeNewFile = curry(
  (buffer, destinationFile) =>
    File.isOrThrow(destinationFile)
    && compose(
      chain(close),
      chain(writeAll(buffer)),
      create
    )(destinationFile)
);

// Calling `writeNewFile` results in an instance of `Task` keeping the function pure.
assert(
  Task.is(writeNewFile(File.fromPath(`${Deno.cwd()}/hoge`), Buffer.fromString("Hello Deno")))
);

// Finally, calling `Task#run` will call copy to the new file and return a promise
writeNewFile(File.fromPath(`${Deno.cwd()}/hoge`), Buffer.fromString("Hello Deno")).run()
  .then(container => {
    // The returned value should be an instance of `Either.Right` or `Either.Left`
    assert(Either.Right.is(container));
    // Forcing to coerce the container to string will show that the final value is our File representation.
    assert(container.toString(), `Either.Right(File("${Deno.cwd()}/piyo", ...))`);
  });

// await copyToNewFile(sourceFile, destinationFile).run() === Either.Right(File a)

File System

โš ๏ธ Note Deno.cwd is used in the following example; if you use Deno.cwd to compose your paths, your functions are no longer pure.

Buffer type

The Buffer type represents a data buffer.

import { Buffer } from "https://deno.land/x/functional_io@v0.3.2/types.js";

const container = Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ]));

Buffer#fromString

The method creates a Buffer from a string.

const container = Buffer.fromString("ABCDE");

This implementation of Buffer is a valid Setoid and Functor.

Directory type

The Directory type represents a directory on the file system.

Itโ€™s only property is its path.

A Directory is a valid Location.

import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";

const container = Directory(`${Deno.cwd()}/hoge`);

This implementation of Directory is a valid Setoid, Functor and Monad.

File type

The File type represents a file on the file system.

It has 3 properties: a path, a buffer and a rid.

A File is a valid Location and Resource.

import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";

const container = File(`${Deno.cwd()}/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), 3);

This implementation of Directory is a valid Setoid, Semmigroup, Functor and Monad.

chdir ๐Ÿ“•

Change the current working directory to the specified path.

chdir :: Directory a -> Task e Directory a

import { chdir } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = chdir(Directory(".."));

assert(Task.is(container));

chmod ๐Ÿ“•

Changes the permission of a specific file/directory of specified path. Ignores the processโ€™s umask.

chmod :: Number -> Location a -> Task e Location a

import { chmod } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = chmod(0o000, File.fromPath(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

chown ๐Ÿ“•

Change owner of a regular file or directory. This functionality is not available on Windows.

chown :: Number -> Number -> Location a -> Task e Location a

import { chown } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = chown(null, null, File.fromPath(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

close ๐Ÿ“•

Close the given resource which has been previously opened, such as via opening or creating a file. Closing a file when you are finished with it is important to avoid leaking resources.

close :: File a -> Task e File a

import { close } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = close(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));

assert(Task.is(container));

copy ๐Ÿ“•

Copies from a source to a destination until either EOF (null) is read from the source, or an error occurs.

copy :: Options -> Buffer a -> Buffer b -> Task e Buffer a

import { copy } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Buffer } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = copy({}, Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])), Buffer(new Uint8Array([])));

assert(Task.is(container));

copyFile ๐Ÿ“•

Copies the contents and permissions of one file to another specified file, by default creating a new file if needed, else overwriting. Fails if target path is a directory or is unwritable.

copyFile :: File a -> File b -> Task e File b

import { copyFile } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = copyFile(File.fromPath(`${Deno.cwd()}/hoge`), File.fromPath(`${Deno.cwd()}/piyo`));

assert(Task.is(container));

create ๐Ÿ“•

Creates a file if none exists or truncates an existing file.

create :: File a -> Task e File a

import { create } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = create(File.fromPath(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

cwd ๐Ÿ“•

Return a Directory representation of the current working directory.

cwd :: () -> Task e Directory a

import { cwd } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = cwd();

assert(Task.is(container));

emptyDir ๐Ÿ“•

Ensures that a directory is empty. Deletes directory contents if the directory is not empty. If the directory does not exist, it is created. The directory itself is not deleted.

emptyDir :: Directory a -> Task e Directory a

import { emptyDir } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = emptyDir(Directory(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

ensureDir ๐Ÿ“•

Ensures that the directory exists. If the directory structure does not exist, it is created. Like mkdir -p.

ensureDir :: Directory a -> Task e Directory a

import { ensureDir } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = emptyDir(Directory(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

exists ๐Ÿ“•

Test whether the given path exists by checking with the file system. If the file or directory doesnโ€™t exist, it will resolve to Either.Left(null).

exists :: Location a -> Task null Location a

import { exists } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = exists(Directory(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

mkdir ๐Ÿ“•

Creates a new directory with the specified path.

mkdir :: Options -> Directory a -> Task e Directory a

import { mkdir } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = mkdir({}, Directory(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

move ๐Ÿ“•

Moves a file or directory.

move :: Options -> String -> Location a -> Task e Location b

import { move } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = move({}, `${Deno.cwd()}/piyo`, Directory(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

open ๐Ÿ“•

Open a file and resolve to an instance of File. The file does not need to previously exist if using the create or createNew open options. It is the callers responsibility to close the file when finished with it.

open :: Options -> File a -> Task e File a

import { open } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = open({ read: true, write: true }, File.fromPath(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

read ๐Ÿ“•

Read from a Resource given it has a non-zero raw buffer.

read :: Resource a -> Task e Resource a

import { read } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = read(File(`${Deno.cwd()}/hoge`, new Uint8Array(5), 3));

assert(Task.is(container));

readAll ๐Ÿ“•

Read from a Resource.

readAll :: Resource a -> Task e Resource a

import { readAll } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Buffer, File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = readAll(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));

assert(Task.is(container));

rename ๐Ÿ“•

Renames a file or directory.

rename :: String -> Location a -> Task e Location b

import { rename } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Directory } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = rename(`${Deno.cwd()}/piyo`, Directory(`${Deno.cwd()}/hoge`));

assert(Task.is(container));

write ๐Ÿ“•

Write to a Resource given it has a non-zero raw buffer.

write :: Resource a -> Task e Resource a

import { write } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = write(
  File(`${Deno.cwd()}/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), _file.rid)
);

assert(Task.is(container));

writeAll ๐Ÿ“•

Write all to a Resource from a Buffer.

writeAll :: Buffer b -> Resource a -> Task e Resource b

import { writeAll } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Buffer, File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = writeAll(
  Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])),
  File(`${Deno.cwd()}/hoge`, new Uint8Array([]), _file.rid)
);

assert(Task.is(container));

writeFile ๐Ÿ“•

Write a File to the file system.

writeFile :: Options -> File a -> Task e File b

import { writeFile } from "https://deno.land/x/functional_io@v0.3.2/fs.js";
import { Buffer, File } from "https://deno.land/x/functional_io@v0.3.2/types.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";

const container = writeFile(
  {},
  File(`${Deno.cwd()}/hoge`, new Uint8Array([]), _file.rid)
);

assert(Task.is(container));

Deno

This codebase uses Deno.

MIT License

Copyright ยฉ 2020 - Sebastien Filion

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.