Skip to main content
Deno 2 is finally here πŸŽ‰οΈ
Learn more

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.