Functional Deno IO
Deno IO methods as valid Task monads perfect to write great point-free software in JavaScript.
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 from "https://deno.land/x/functional_io@v0.4.2/library/Buffer.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.js";
import { close, writeAll, create } from "https://deno.land/x/functional_io@v0.4.2/library/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)
Buffer
The Buffer
is the most basic type; it only has one attribute which is a typed array named โrawโ.
Any type that share the raw attribute is composable with Buffer
(and each other) and interoperable.
The Buffer
type implements the following algebras:
- Group
- Comonad
- Monad
Example
const buffer = Buffer.fromString("Hoge").concat(Buffer.fromString("Fuga"));
assert(Buffer.is(buffer));
Directory
The Directory
type represents a directory on the file system. It is the only type with the same shape as URL
.
It has only one attributes: the path of the directory.
A Directory
is interoperable with a URL
or a File
.
It also has interoperability with a File
through the FileSystemCollection
type.
The Directory
type implements the following algebras:
- Ord
- Comonad
- Monad
Example
assert(Directory(`${Deno.cwd()}/hoge`).lte(Directory(`${Deno.cwd()}/piyo`)));
File
The File
type extends the Resource
type. It represents a file with a path.
It has three attributes: the first is the path of the file, the second is a typed array named โrawโ and the last
is the Resource ID (rid
).
A File
is composable and interoperable with a Resource
or a Buffer
โ It also has some interoperability with a
Location
through the FileSystemCollection
.
The File
type implements the following algebras:
- Group
- Bifunctor
- Comonad
- Monad
Example
const file = File(`${Deno.cwd()}/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), 3)
.concat(File(`${Deno.cwd()}/piyo`, new Uint8Array([ 70, 71, 72, 73, 74 ]), 3));
assert(File.is(file));
File System Collection
The FileSystemCollection
is represents a collection of Location
, namely of Directory
and File
. This of it
as an Array for those types.
The FileSystemCollection
type implements the following algebras:
- Group
- Comonad
- Monad
- Traversable
โ ๏ธ The FileSystemCollection
type as a traversable is experimental.
Example
const containerA = Maybe.Just(42).map(x => x + 2);
const containerB = Maybe.Nothing.map(x => x + 2);
assert(Maybe.Just.is(containerA));
assert(containerA.extract() === 44);
assert(Maybe.Nothing.is(containerB));
Request
The Request
represent a HTTP request.
It has two attributes: the first is an object for the response โheaderโ and the second is a typed array named โrawโ.
The Request
type is mostly interoperable with Resource
, File
and Response
.
The Resource
type implements the following algebras:
- Group
- Bifunctor
- Monad
Example
const request = Request({}, new Uint8Array([ 65, 66, 67, 68, 69 ]))
.concat(Resource(new Uint8Array([ 70, 71, 72, 73, 74 ]), 3));
assert(Request.is(request));
Utilities
The Request
namespace comes with 4 methods for convenience to create an instance of Request
with a common verb.
The methods are curried when necessary. Object -> Unint8Array -> Response
const container = compose(
lift(Request.post({ ["Content-Type"]: "application/json" })),
readFile
)(File.fromPath(`${Deno.cwd()}/hoge`));
assert((await container.run()).extract().headers.method === "POST");
Method name | Has 2 arguments |
---|---|
delete / DELETE |
false |
get / GET |
false |
post / POST |
true |
put / PUT |
true |
โข The capitalized version of the methods were added because delete
is a TypeScript reserved word.
Resource
The Resource
type extends the Buffer
type. It represents a system resource with a handle, eg: STDOUT, STDIN or a
file. It has two attributes: the first is a typed array named โrawโ and the second is the Resource ID (rid
).
Any type that share the Resource
attributes is composable and interoperable.
The Resource
type implements the following algebras:
- Group
- Bifunctor
- Comonad
- Monad
Example
const resource = Resource(new Uint8Array([ 65, 66, 67, 68, 69 ]), 3)
.concat(Resource(new Uint8Array([ 70, 71, 72, 73, 74 ]), 3));
assert(Resource.is(resource));
Response
The Response
represent a HTTP response.
It has two attributes: the first is an object for the response โheaderโ and the second is a typed array named โrawโ.
The Response
type is mostly interoperable with Resource
, File
and Request
.
The Resource
type implements the following algebras:
- Alternative
- Group
- Bifunctor
- Monad
Example
const response = Response.Success({}, new Uint8Array([ 65, 66, 67, 68, 69 ]))
.concat(Resource(new Uint8Array([ 70, 71, 72, 73, 74 ]), 3));
assert(Response.is(response));
Utilities
The Response
namespace comes with 38 methods for convenience to create an instance of Response
with a common
status.
The methods are curried: Object -> Uint8Array -> Response
const container = compose(
lift(Response.OK({ ["Content-Type"]: "application/json" })),
readFile
)(File.fromPath(`${Deno.cwd()}/hoge`));
assert((await container.run()).extract().headers.status === 200);
Method name | Status |
---|---|
OK |
200 |
Created |
201 |
Accepted |
202 |
NoContent |
204 |
MultipleChoice |
300 |
MovePermanently |
301 |
Found |
302 |
NotModified |
304 |
TemporaryRedirect |
307 |
PermanentRedirect |
308 |
BadRequest |
400 |
Unauthorized |
401 |
Forbidden |
403 |
NotFound |
404 |
MethodNotAllowed |
405 |
NotAcceptable |
406 |
RequestTimeout |
408 |
Conflict |
409 |
Gone |
410 |
ImATeapot |
418 |
InternalServerError |
500 |
NotImplemented |
501 |
BadGateway |
502 |
ServiceUnavailable |
503 |
GatewayTimeout |
504 |
PermissionDenied |
550 |
URL
The URL
type represents an URL; either of a location on the file system or on a remote server.
It has only one attributes: the path of the URL.
A URL
is interoperable with a File
or a Directory
.
It also has interoperability with a File
or a Directory
through the FileSystemCollection
type.
The URL
type implements the following algebras:
- Ord
- Comonad
- Monad
Example
assert(URL(`${Deno.cwd()}/hoge`).lte(URL(`${Deno.cwd()}/piyo`)));
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.
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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import Buffer from "https://deno.land/x/functional_io@v0.4.2/library/Buffer.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.4.2/library/fs.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import Buffer from "https://deno.land/x/functional_io@v0.4.2/library/Buffer.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import Directory from "https://deno.land/x/functional_io@v0.4.2/library/Directory.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.4.2/library/fs.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import Buffer from "https://deno.land/x/functional_io@v0.4.2/library/Buffer.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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.4.2/library/fs.js";
import Buffer from "https://deno.land/x/functional_io@v0.4.2/library/Buffer.js";
import File from "https://deno.land/x/functional_io@v0.4.2/library/File.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));
Browser Safe
fetch
Fetches a resource on a local/remote server.
fetch :: Request a -> Task e Response b
import { fetch } from "https://deno.land/x/functional_io@v0.4.2/library/browser-safe.js";
import Request from "https://deno.land/x/functional_io@v0.4.2/library/Request.js";
import Response from "https://deno.land/x/functional_io@v0.4.2/library/Response.js";
import Task from "https://deno.land/x/functional@v1.0.0/library/Task.js";
const containerA = fetch(Request.GET("http://localhost:8000"));
assert(Task.is(containerA));
const containerB = await container.run().extract();
assert(Response.Success.is(containerB));
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.