adapter

A TypeScript utility for writing I/O agnostic programs

Usage no npm install needed!

<script type="module">
  import adapter from 'https://cdn.skypack.dev/adapter';
</script>

README

🔌 adapter

npm version github actions min size conventional commits

write I/O agnostic utilities

What is it?

adapter is a tiny TypeScript helper for writing I/O agnostic utilities in a standardized way. Your code could run in a headless process, an interactive commandline tool, or in a graphical program, but either way your code remains the same.

The idea is difficult to appreciate without an example. If you're comfortable with writing your own Promises and async/await patterns, the following example should be fairly intuitive.

Example

Let's say that you have some code that creates a remote repository via the GitHub API. It might look roughly like this:

import axios from 'axios';    // http library

const url = 'https://api.github.com/user/repos';
const headers = { Authorization: 'token <access-token>' };
const data = { name: '<repo-name>', private: true };

await axios.post(url, data, {headers});

adapter can be used to improve this code by injecting (or "plugging in") the means of handling I/O (input/output), just as Promises inject the means of handling success/failure (resolve/reject). Let's rewrite the above code leveraging these injections:

// github-service.ts

import axios, {AxiosResponse} from 'axios';
import {makeAdapter} from 'adapter';

type Resolve = AxiosResponse;
type Input = {
    types: { 'text': string, 'yes-no': boolean },
    options: {
        [type: string]: { message: string }
    },
    keys: {
        'access-token': 'text',
        'repo-name': 'text',
        'private': 'yes-no',
        'retry': 'yes-no',
    }
};
type Output = string;

export const createRepo = () => makeAdapter<Resolve, Input, Output>(
    async (input, output) => {
        const url = 'https://api.github.com/user/repos';
        const headers = {
            Authorization: 'token ' + await input(
                'access-token', 'text', {message: 'Personal Access Token: '}
            )
        };
        const data = {
            name: await input(
                'repo-name', 'text', {message: 'Repository Name: '}
            ),
            private: await input(
                'private', 'yes-no', {message: 'Private?: '}
            )        	
        };
        
        output('Making API call to GitHub...');
        let response;
        try {
            response = await axios.post(url, data, {headers});
        } catch (e) {
            output('Something went wrong... Status code: ' + e.response.status);
            if (await input('retry', 'yes-no', {message: 'Would you like to try again? '})) {
                return await createRepo().attach({input, output});
            } else {
                throw e;
            }
        }
        
        output('Repository created!');
        return response;
    }
);

Now that our input and output functions are injected, we can use this service anywhere. Say we want to use it as part of a CLI application:

// cli-app.ts

import {prompt} from 'inquirer';    // inquirer is tool for getting cli input
import {createRepo} from './github-service';

const then = console.info;
const output = console.log;
const input = async (type, key, options) => {
    if (type === 'text') type = 'input';
    if (type === 'yes-no') type = 'confirm';
    
    return await prompt([{
        name: key,
        type: type,
        message: options.message
    }]);
};

createRepo()                        // calling our service gives us an `Adapter`
    .attach({then, output, input})  // here we "plug in" our attachments
    .exec();                        // and this executes our code

We could also utilize the same service in a web application:

// browser-app.ts

import service from './service';

service()                      // you can also attach handlers independently,
    .then(console.info)        // like so:
    .catch(console.error)
    .output(console.log)
    .input(async (type, key, options) => {
        if (type === 'yes-no') {
            const yn = window.prompt(options.message + '(Y/n)');
            return yn === 'y';
        } else {
            return window.prompt(options.message);
        }
    })
    .exec();

Hopefully seeing these two applications side by side illustrates the advantages of using adapter.

Installation

npm install adapter

or

yarn add adapter

Documentation

More complete documentation is coming soon!

Author

Tanner Nielsen tannerntannern@gmail.com

Website | GitHub