npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fp-ts-bootstrap

v0.1.0

Published

Application bootstrapping utilities for fp-ts

Downloads

114,791

Readme

FP-TS Bootstrap

This is a module aimed at application bootstrapping using types from fp-ts. Its ideas and most of the code were ported from the fluture-hooks library.

This module mainly provides a Bracket type with accompanying type class instances. The Bracket type is a drop-in replacement for the Cont type from fp-ts-cont, but specialized in returning TaskEither. This solves the problem stipulated at the end of application bootstrapping with fp-ts by allowing the return type to be threaded through the program. Furthermore, it makes the ApplicativePar instance possible, which allows for parallel composition of bracketed resources.

Besides the Bracket type, this module also provides a Service type which is a small layer on top for managing dependencies through the Reader monad.

Example

Define your service. See the full example in ./example/services/server.ts.

export const withServer: Service.Service<Error, Dependencies, HTTP.Server> = (
  ({port, app}) => Bracket.bracket(
    () => new Promise(resolve => {
      const server = HTTP.createServer(app);
      server.listen(port, () => resolve(E.right(server)));
    }),
    server => () => new Promise(resolve => {
      server.close((e: unknown) => resolve(
        e instanceof Error ? E.left(e) : E.right(undefined)
      ));
    }),
  )
);

Combine multiple such services with ease using Do notation. See the full example in ./example/services/index.ts.

export const withServices = pipe(
  withEnv,
  Bracket.bindTo('env'),
  Bracket.bind('logger', ({env}) => withLogger({level: env.LOG_LEVEL})),
  Bracket.bind('database', ({env, logger}) => withDatabase({
    url: env.DATABASE_URL,
    logger: logger
  })),
  Bracket.bind('app', ({database}) => withApp({database})),
  Bracket.bind('server', ({env, app}) => withServer({
    port: env.PORT,
    app: app,
  })),
);

Consume your service. See the full example in ./example/index.ts.

const program = withServices(({server, logger}) => pipe(
  TE.fromIO(logger.info(`Server listening on ${JSON.stringify(server.address())}`)),
  TE.apSecond(TE.fromTask(() => new Promise(resolve => {
    process.once('SIGINT', resolve);
  }))),
  TE.chain(() => TE.fromIO(logger.info('Shutting down app'))),
));

And finally, run your program:

program().then(E.fold(console.error, console.log), console.error);

Types

Bracket

import {Bracket} from 'fp-ts-bootstrap';
type Bracket<E, R> = (
  <T>(consume: (resource: R) => TaskEither<E, T>) => TaskEither<E, T>
);

The Bracket type aliases the structure that's encountered when using a curried variant of fp-ts' TaskEither.bracket function. This curried variant is also exported from the Bracket module as bracket. It models a bracketed resource for which the consumption hasn't been specified yet.

The Bracket module defines various type class instances for Bracket that allow you to compose and combine multiple bracketed resources. From most instances, some derivative functions are exported as well.

  • Pointed: of, Do
  • Functor: map, flap, bindTo, let
  • Apply: ap, apFirst, apSecond, apS, getApplySemigroup, sequenceT, sequenceS
  • Applicative: Pointed Apply
  • Chain: chain, chainFirst, bind
  • Monad: Pointed Chain
  • ApplyPar: apPar, apFirstPar, apSecondPar, apSPar, getApplySemigroupPar, sequenceTPar, sequenceSPar
  • ApplicativePar: Pointed ApplyPar

Service

import {Service} from 'fp-ts-bootstrap';
type Service<E, D, S> = Reader<D, Bracket<E, S>>;

The Service type is a small layer on top of Reader that formalizes the type of a Bracket with dependencies. The Service type can also be composed and combined using the utilities provided by ReaderT<Bracket>. These utilities are re-exported from the Service module.

Cookbook

Defining a service with acquisition and disposal

import * as FS from 'fs/promises';
import * as TE from 'fp-ts/TaskEither';
import * as E from 'fp-ts/Either';
import {Bracket} from 'fp-ts-bootstrap';

const acquireFileHandle = (url: string) => (
  TE.tryCatch(() => FS.open(url, 'a'), E.toError)
);

const disposeFileHandle = (file: FS.FileHandle) => (
  TE.tryCatch(() => file.close(), E.toError)
);

const withMyFile = Bracket.bracket(
  acquireFileHandle('/tmp/my-file.txt'),
  disposeFileHandle,
);

Defining a service with dependencies

This recipe builds on the previous one by adding dependencies to the service.

import {Service} from 'fp-ts-bootstrap/lib/Service';

type Dependencies = {
  url: string;
};

const withMyFile: Service<Error, Dependencies, FS.FileHandle> = (
  ({url}) => Bracket.bracket(
    acquireFileHandle(url),
    disposeFileHandle,
  )
);

Combining services in parallel

The Bracket type has a sequential Applicative instance that it uses by default, but there's also a parallel ApplicativePar instance that you can use to combine services in parallel*. Two very useful derivative function using ApplicativePar are

  • sequenceSPar for building a Struct of resources from a Struct of Brackets; and
  • apSPar for adding another property to an existing Struct of services:
import {pipe} from 'fp-ts/function';
import {Bracket} from 'fp-ts-bootstrap';

const withServices = pipe(
  Bracket.sequenceSPar({
    env: withEnv,
    logger: withLogger({level: 'info'}),
  }),
  Bracket.apSPar('database', withDatabase({url: 'postgres://localhost:5432'}))
);

const program = withServices(({env, logger, database}) => pipe(
  // ...
));

* By "in parallel" we mean that the services are acquired in parallel, but disposed in sequence. This is a technical limitation that exists to ensure that the ApplyPar instance is lawful.

Threading dependencies during service composition

import {pipe} from 'fp-ts/function';
import {Bracket} from 'fp-ts-bootstrap';

const withServices = pipe(
  withEnv,
  Bracket.bindTo('env'),
  Bracket.bind('logger', ({env}) => withLogger({level: env.LOG_LEVEL})),
  Bracket.bind('database', ({env, logger}) => withDatabase({
    url: env.DATABASE_URL,
    logger: logger
  })),
  Bracket.bind('server', ({env, database}) => withServer({
    port: env.PORT,
    app: app,
    database: database,
  })),
);

Creating a full-fledged program by composing services

There's a fully working example app in the ./example directory. To run it, clone this repo and run the following commands:

$ npm install
$ ./node_modules/.bin/ts-node ./example/index.ts

You should now be able to visit http://localhost:3000/arbitrary/path, which should give you a Hello World response, and log your request URL to ./database.txt.