IO methods as valid Task monads perfect to write great point-free software in JavaScript that is compatible with most modern browsers and Deno.
- Buffer
- Directory
- File
- File System Collection
- Request
- Resource
- Response
- URL
- Browser safe
- File System
- Utilities
- TypeScript
This example uses the Ramda library - for simplification - but you should be able to use any library that implements the Fantasy-land specifications.
import { __, ap, chain, compose, lift, map, match, path, prop, useWith } from "https://deno.land/x/[email protected]/mod.ts";
import Task from "https://deno.land/x/[email protected]/library/Task.js";
import { safeExtract } from "https://deno.land/x/[email protected]/library/utilities.js";
import Request from "https://deno.land/x/[email protected]/library/Request.js";
import { factorizeFile } from "https://deno.land/x/[email protected]/library/File.js";
import { fetch } from "https://deno.land/x/[email protected]/library/browser_safe.js";
import { writeFile } from "https://deno.land/x/[email protected]/library/fs.js";
const fetchBacon = compose(
chain(writeFile({})),
ap(
useWith(
lift(factorizeFile(__, __, 0)),
[
compose(
Task.of,
name => `${Deno.cwd()}/${name}.html`,
prop(1),
match(/\?type=([A-Za-z\-]+)/),
path([ "headers", "url" ])
),
map(prop("raw"))
]
),
fetch
)
);
const container = fetchBacon(
Request.get("https://baconipsum.com/api/?type=all-meat¶s=3&start-with-lorem=1&format=html")
);
// Calling `fetchBacon` results in an instance of `Task` keeping the function pure.
assert(Task.is(container));
const file = safeExtract("Failed to write file.", await container.run());
assert(File.is(file));
As a convenience, when using Functional IO in the browser, you can use the unminified bundled copy (18KB gzipped).
import { Task, safeExtract } from "https://deno.land/x/[email protected]/functional.js";
import { Request, Response, fetch } from "https://deno.land/x/[email protected]/functional-io.js";
const container = fetch(
Request.get("https://baconipsum.com/api/?type=all-meat¶s=3&start-with-lorem=1&format=html")
);
assert(Task.is(container));
const response = safeExtract("Failed to fetch resource.", await container.run());
assert(Response.is(response));
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
import Buffer from "https://deno.land/x/[email protected]/library/Buffer.js";
const buffer = Buffer.fromString("hoge").concat(Buffer.fromString("fuga"));
assert(Buffer.is(buffer));
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
assert(Directory(`${Deno.cwd()}/hoge`).lte(Directory(`${Deno.cwd()}/piyo`)));
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
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));
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
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));
File Group algebra law "Left identity" and "Right identity" is not respected because the rid can't be modified without using bimap.
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
const request = Request({}, new Uint8Array([ 65, 66, 67, 68, 69 ]))
.concat(Resource(new Uint8Array([ 70, 71, 72, 73, 74 ]), 3));
assert(Request.is(request));
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 |
get |
GET |
post |
POST |
put |
PUT |
✢ The capitalized version of the methods were added because delete
is a TypeScript reserved word.
Request Semigroup algebra law "Left identity" is not respected because the headers can't be modified without using bimap.
Request Monoid algebra law "Left identity" is not respected because the headers can't be modified without using bimap.
Request Group algebra law "Left identity" and "Right identity" is not respected because the headers can't be modified without using bimap.
Request Modnad algebra law "Right identity" is not respected because the headers can't be modified without using bimap.
Request Applicative algebra law "Interchange" is not respected because the headers can't be modified without using bimap.
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
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));
Resource Monoid algebra law "Left identity" is not respected because the rid can't be modified without using bimap.
Resource Group algebra law "Left identity" and "Right identity" is not respected because the rid can't be modified without using bimap.
Resource Modnad algebra law "Right identity" is not respected because the rid can't be modified without using bimap.
Resource Applicative algebra law "Interchange" is not respected because the rid can't be modified without using bimap.
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
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));
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 |
Response Semigroup algebra law "Left identity" is not respected because the headers can't be modified without using bimap.
Response Monoid algebra law "Left identity" is not respected because the headers can't be modified without using bimap.
Response Group algebra law "Left identity" and "Right identity" is not respected because the headers can't be modified without using bimap.
Response Modnad algebra law "Right identity" is not respected because the headers can't be modified without using bimap.
Response Applicative algebra law "Interchange" is not respected because the headers can't be modified without using bimap.
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
assert(URL(`${Deno.cwd()}/hoge`).lte(URL(`${Deno.cwd()}/piyo`)));
Request → Task e Response
Fetches a resource on a local/remote server.
import { fetch } from "https://deno.land/x/[email protected]/library/browser-safe.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.cwd
is used in the following example; if you use Deno.cwd
to compose your paths, your functions
are no longer pure.
chdir
📕
Directory → Task e Directory
Change the current working directory to the specified path.
import { chdir } from "https://deno.land/x/[email protected]/library/fs.js";
const container = chdir(Directory(".."));
assert(Task.is(container));
chmod
📕
Number → File → Task e File
Changes the permission of a specific file/directory of specified path. Ignores the process's umask.
import { chmod } from "https://deno.land/x/[email protected]/library/fs.js";
const container = chmod(0o000, File.fromPath(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
chown
📕
Number → Number → File → Task e File
Change owner of a regular file or directory. This functionality is not available on Windows.
import { chown } from "https://deno.land/x/[email protected]/library/fs.js";
const container = chown(null, null, File.fromPath(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
close
📕
Resource → Task e Resource
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.
import { close } from "https://deno.land/x/[email protected]/library/fs.js";
const container = close(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));
assert(Task.is(container));
copy
📕
Object → Buffer a → Buffer b → Task e Buffer b
Copies from a source to a destination until either EOF (null) is read from the source, or an error occurs.
import { copy } from "https://deno.land/x/[email protected]/library/fs.js";
const container = copy({}, Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])), Buffer(new Uint8Array([])));
assert(Task.is(container));
copyFile
📕
File a → File b → Task e File b
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.
import { copyFile } from "https://deno.land/x/[email protected]/library/fs.js";
const container = copyFile(File.fromPath(`${Deno.cwd()}/hoge`), File.fromPath(`${Deno.cwd()}/piyo`));
assert(Task.is(container));
create
📕
File → Task e File
Creates a file if none exists or truncates an existing file.
import { create } from "https://deno.land/x/[email protected]/library/fs.js";
const container = create(File.fromPath(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
cwd
📕
Return a Directory representation of the current working directory.
() → Task e Directory
import { cwd } from "https://deno.land/x/[email protected]/library/fs.js";
const container = cwd();
assert(Task.is(container));
ensureDir
📕
Directory → Task e Directory
Ensures that the directory exists. If the directory structure does not exist, it is created. Like mkdir -p
.
import { ensureDir } from "https://deno.land/x/[email protected]/library/fs.js";
const container = emptyDir(Directory(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
exists
📕
`URL → Task e|null URL
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)
.
import { exists } from "https://deno.land/x/[email protected]/library/fs.js";
const container = exists(Directory(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
mkdir
📕
Object → Directory → Task e Directory
Creates a new directory with the specified path.
import { mkdir } from "https://deno.land/x/[email protected]/library/fs.js";
const container = mkdir({}, Directory(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
move
📕
Object → String → URL → Task e URL
Moves a file or directory.
import { move } from "https://deno.land/x/[email protected]/library/fs.js";
const container = move({}, `${Deno.cwd()}/piyo`, Directory(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
open
📕
Object → File → Task e File
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.
import { open } from "https://deno.land/x/[email protected]/library/fs.js";
const container = open({ read: true, write: true }, File.fromPath(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
read
📕
Resource Task e Resource
Read from a Resource given it has a non-zero raw buffer.
import { read } from "https://deno.land/x/[email protected]/library/fs.js";
const container = read(File(`${Deno.cwd()}/hoge`, new Uint8Array(5), 3));
assert(Task.is(container));
Resource → Task e Resource
Read from a Resource to the CLRF.
import { readLine } from "https://deno.land/x/[email protected]/library/fs.js";
const container = readLine(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));
assert(Task.is(container));
Number → Resource → Task e Resource
Read N bytes from a Resource.
import { readNBytes } from "https://deno.land/x/[email protected]/library/fs.js";
const container = readNBytes(5, File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));
assert(Task.is(container));
Resource → Task e Resource
Read 1 byte from a Resource.
import { readOneByte } from "https://deno.land/x/[email protected]/library/fs.js";
const container = readOneByte(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));
assert(Task.is(container));
readAll
📕
Resource → Task e Resource
Read from a Resource.
import { readAll } from "https://deno.land/x/[email protected]/library/fs.js";
const container = readAll(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));
assert(Task.is(container));
readFile
📕
File → Task e File
Read from a File.
import { readFile } from "https://deno.land/x/[email protected]/library/fs.js";
const container = readFile(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));
assert(Task.is(container));
remove
📕
Object → URL → Task e URL
Removes the named file or directory.
import { remove } from "https://deno.land/x/[email protected]/library/fs.js";
const container = remove({ recursive: true }, Directory.fromPath(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
rename
📕
String → URL → Task e URL
Renames a file or directory.
import { rename } from "https://deno.land/x/[email protected]/library/fs.js";
const container = rename(`${Deno.cwd()}/piyo`, Directory(`${Deno.cwd()}/hoge`));
assert(Task.is(container));
write
📕
Resource → Task e Resource
Write to a Resource given it has a non-zero raw buffer.
import { write } from "https://deno.land/x/[email protected]/library/fs.js";
const container = write(File(`${Deno.cwd()}/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), 3));
assert(Task.is(container));
writeAll
📕
Buffer → Task e Resource
Write all to a Resource from a Buffer.
import { writeAll } from "https://deno.land/x/[email protected]/library/fs.js";
const container = writeAll(
Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])),
File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3)
);
assert(Task.is(container));
writeFile
📕
Object → File → Task e File
Write a File to the file system.
import { writeFile } from "https://deno.land/x/[email protected]/library/fs.js";
const container = writeFile({}, File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3));
assert(Task.is(container));
Uint8Array → Number
This function takes a Uint8Array
and, returns the index of the last character of the first CLRF sequence
encountered.
import { findCLRFIndex } from "https://deno.land/x/[email protected]/library/utilities.js";
assertEquals(findCLRFIndex(new Uint8Array([ 104, 111, 103, 101, 13, 10 ])), 6);
Uint8Array → Uint8Array
This function takes a Uint8Array
and, returns the typed array minus the first line separated by CLRF.
import { discardFirstLine } from "https://deno.land/x/[email protected]/library/utilities.js";
assertEquals(
discardFirstLine(new Uint8Array([ 104, 111, 103, 101, 13, 10, 104, 111, 103, 101, 13, 10 ])),
new Uint8Array([ 104, 111, 103, 101, 13, 10 ])
);
Number → Uint8Array → Uint8Array
This function takes a Number, a Uint8Array
and, returns the typed array minus the specified amount of character
starting from the left side.
import { discardNCharacter } from "https://deno.land/x/[email protected]/library/utilities.js";
assertEquals(
discardNCharacter(1, new Uint8Array([ 104, 111, 103, 101, 13, 10 ])),
new Uint8Array([ 111, 103, 101, 13, 10 ])
);
Uint8Array → Uint8Array
This function takes a Uint8Array
and, returns the first line separated by a CLRF inclusively.
import { getFirstLine } from "https://deno.land/x/[email protected]/library/utilities.js";
assertEquals(
getFirstLine(new Uint8Array([ 104, 111, 103, 101, 13, 10, 104, 111, 103, 101, 13, 10 ])),
new Uint8Array([ 104, 111, 103, 101, 13, 10 ])
);
Uint8Array[] → Uint8Array
This function takes a list of Uint8Array
and, returns a Uint8Array
of the list joined with CLRF sequence; the
function is analogous to Array#join
.
import { joinCLRF } from "https://deno.land/x/[email protected]/library/utilities.js";
assertEquals(
joinCLRF(
[
new Uint8Array([ 104, 111, 103, 101 ]),
new Uint8Array([ 104, 111, 103, 101 ])
]
),
new Uint8Array([ 104, 111, 103, 101, 13, 10, 104, 111, 103, 101, 13, 10 ])
);
Uint8Array → Uint8Array[]
This function takes a Uint8Array
and, returns a list of Uint8Array
of subarray split at the CLRF sequence; the
function is analogous to String#split
.
import { splitCLRF } from "https://deno.land/x/[email protected]/library/utilities.js";
assertEquals(
splitCLRF(new Uint8Array([ 104, 111, 103, 101, 13, 10, 104, 111, 103, 101, 13, 10 ])),
[
new Uint8Array([ 104, 111, 103, 101, 13, 10 ]),
new Uint8Array([ 104, 111, 103, 101, 13, 10 ])
]
);
Uint8Array → Uint8Array
This function takes a Uint8Array
and, returns a typed array minus CRLF at the beginning and at the end;
the function is analogous to String#trim
.
import { trimCRLF } from "https://deno.land/x/[email protected]/library/utilities.js";
assertEquals(
trimCRLF(new Uint8Array([ 104, 111, 103, 101, 13, 10 ])),
new Uint8Array([ 104, 111, 103, 101 ])
);
`Number|Array|Uint8Array → Uint8Array
This function factorize a Uint8Array given an argument.
You can import any types.
import {
Buffer,
Directory,
File,
Request,
Resource,
Response,
URL
} from "https://deno.land/x/[email protected]/mod.ts";
Or, you can import individual sub-module with the appropriate TypeScript hint in Deno.
// @deno-types="https://deno.land/x/[email protected]/library/Request.d.ts"
import Request from "https://deno.land/x/[email protected]/library/Request.js";
We appreciate your help! Please, read the guidelines.
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.