Skip to content
This repository has been archived by the owner on Feb 22, 2021. It is now read-only.
/ iodio Public archive

Pure Functional Monadic Lazy Query Builder based on Knex,js powered by Fluture

License

Notifications You must be signed in to change notification settings

FbN/iodio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DISCONTINUED: please use Futurity


IODIO

Pure Functional Monadic Lazy Query Builder

Wraps Fluture

Powered by Fluture

Fluture Implements Fantasy Land:
Functor, Bifunctor, Apply, Applicative, Chain, Monad

Table of Contents

About The Project

I really love Knex.js Query Builder. It is really easy to set up and use. It's Promise based and offers a clean, fluent way to interact with the Database engine of your choice (Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle, and Amazon Redshift).

Some aspects of Knex I like less:

  • Mutable. Every time you specialize in your query calling some method (.where, .limit, .join, etc.) you are mutating the same instance. This makes it harder to compose your apps.

  • Not Lazy. Being based on Promise, the query is materially executed the moment you call .resolve on the builder or try to "await" for the result. You cannot composer your program in a Pure way leaving the actual "impure" query execution as the last step.

So, let's introduce you IODIO. It's a monadic wrapper of a Knex query builder and a Fluture Future that represents the result of the query. It's lazy and pure, so you can program in advance your computations (query composition and/or result transformation) and run all as the last step.

You can see it as a lazy Bifunctor, the left side represents the Knex QueryBuilder, and the right side the Fluture future value that wraps the results. Plus we have a simple object storing the query input parameters.

Built With

Iodio is implemented in a few lines of code that mix up some good libraries:

Getting Started

Installation

  1. Add Iodio to your project
yarn add iodio
  1. If not already in your project add peer dependencies
yarn add knex
yarn add fluture
  1. Do not forget your DB connection layer, ex. sqlite3
yarn add sqlite3

Usage

Node.js "Hello World".

import Knex from 'knex'
import Iodio from 'iodio'

// Init the Knex DB connection config
const db = Knex({
    client: 'sqlite3',
    connection: {
        filename: 'tests/chinook.sqlite'
    },
    useNullAsDefault: true
})

// Construct Iodio object. Lift take DB connection and a params list.
// Params are passed directly to knex: tableName, options={only: boolean}
const q = Iodio.lift(db, ['Track'])

// Then you can start working on your query
// (qb is knew QueryBuilder)
const q2 = q.qMap(qb => qb.limit(3))

// Lets transform the result
// res is the query result list
const q3 = q2.map(res => res.map(row => row.Name + ' by  ' + row.Composer))

// Let's filter by Composer
// qMap second param 'p' is the Iodio params object
const q4 = q3.qMap((qb, p) => qb.where({ Composer: p.Composer }))

// Get a Iodio binded to Composer 'Red Hot Chili Peppers'
const redHot = q4.pMap(() => ({
    Composer: 'Red Hot Chili Peppers'
}))

// Get a Iodio binded to Composer 'Kurt Cobain'
const cobain = q4.pMap(() => ({
    Composer: 'Kurt Cobain'
}))

// I want to work on the result of the two query
// we use async/await as DO Notation
const redHotCobain = Iodio.merge([redHot, cobain])(async (redHot, cobain) => [
    ...(await redHot),
    ...(await cobain)
])

// N.B. no real query is actually executed at this point, it's all lazy
//
// To run all the constructed computations and effects
// we call fork
redHotCobain.fork(console.error)(console.log)

Output:

[
  'Parallel Universe by  Red Hot Chili Peppers',
  'Scar Tissue by  Red Hot Chili Peppers',
  'Otherside by  Red Hot Chili Peppers',
  'Intro by  Kurt Cobain',
  'School by  Kurt Cobain',
  'Drain You by  Kurt Cobain'
]

API

Factories

Methods

Consuming / Collapsing Methods

Factories

of(pPred, qbR, fR)

Iodio base constructor. It's rare to call it directly, you can call other more confortable constructors: lift, resolve, merge. (pPred, qbR, fR)

Arguments:

  • pPred: Function Paramas Predicate: a function used to bind query params to Iodio monad. Take current params map as argument and return a new param map.

  • qbR: ParamsFunction, QueryBuilderT<T> QueryBuilder Reader: Monet Reader monad that wraps the knex query builder.

  • fR: QueryBuilderT<T>, FutureInstanceArrayT<T> Fluture Reader: Monet Reader monad that wraps the Fluture Future representing the query output.

Returns: IodioInstance


lift(db, args)

Contruct a new IodioInstance setting knex db connection object and an array of params to pass to knex to get the QueryBuilder.

Arguments:

  • db: knex The knex configured DB Connection.

  • args: Array<any> An array to pass to knex connection to init the QueryBuilder, usually is the tableName plus other options ['TableName'].

Returns: IodioInstance


resolve(db, args)

Contruct an IodioInstance resolved to a fixed value. Working on QueryBuilder of the resulting monad currently not supported.

Arguments:

  • v: any The resolution value.

Returns: IodioInstance


merge(iodio1, iodio2, ...)(doAsyncFunction)

Useful to merge the result of two or more iodio instance and work on the values. It's like a DO notation implemented with async/await. Take present that the merge it's lazy. So a iodio passed to merge is not resolved (collapsed) until we request it's value in the supplied async function.

Arguments:

  • iodio1, iodio2, ...: IodioInstance The IodioInstance wrapping the results we want to work with in the supplied async function

  • doAsyncFunction: (Promise<iodio1 res>, Promise<iodio2 res>, ...) => Promise<res> The async function that let us work with the requested iodio value.

Returns: IodioInstance


Methods

pMap(pred)

Params Map. Function to transform the query params object binded to the monad.

Arguments:

  • pred: (a: Object=> a: Object) Function that has the current binded params object as input and return the updated params object as output.

Returns: IodioInstance


qMap(pred)

Query Mapper. Function to transform the knex wrapped QueryBuilder object. You can use this method to compose your query (working on Left side of the Bifunctor) without altering the future result transformations (Right side of the Bifunctor).

Arguments:

  • pred: ((qb: QueryBuilder, p: Object) => QueryBuilder) For your confort the params object is passed as second argumet so you can reference binded params in the query.

Returns: IodioInstance


map(pred)

Fluture Future Mapper. Function to work on the knex wrapped Fluture future monad. You can use this method to compose computations on the future result (working on Right side of the Bifunctor) without altering the QueryBuilder (Left side of the Bifunctor).

Arguments:

  • pred: (f: FutureInstance => f: FutureInstance)

Returns: IodioInstance


bimap(qPred, fPred)

It's like calling qMap and Map at the same time. Let you work on the two side of the Bifunctor.

Arguments:

  • qPred: ((qb: QueryBuilder, p: Object) => QueryBuilder)
  • fPred: (f: FutureInstance => f: FutureInstance)

Returns: IodioInstance


chain(pred)

Like map but the supplied function must return an IodioInstance. The computation will continue with the new IodioInstance.

Arguments:

  • pred Function returning an IodioInstance

Returns: IodioInstance


ap(iodioFuntion)

Call the function wrapped as future value in the supplied IodioInstance passing the self future value as parameter.

Arguments:

  • iodioFuntion: IodioInstance An Iodio Instance that wraps a function as future value.

Returns: IodioInstance


future()

Transform Iodio to a Fluture Future wich wrapped value equals query result plus computation. No query is executed calling this method, query and computation will be done at time of forking returned future.

Returns: FutureInstance


Consuming / Collapsing Methods

fork(left)(right)

Make the query collapse and excute it's effects on the Database. The results will be computed by the Fluture Future object and result passed to the right predicate. In case of error the left predicate is called.

Arguments:

  • left Error predicate.
  • right Error predicate.

Returns: Cancel


toString()

Debug/Inspection method. Collapse the QueryBuilder (without running the actual query) and the Fluture Future, printing a string rapresentation of both and binded params map.

Returns: String


first()

Shortcut for testing purpouse. Make the IodioInstance collapse, exectuing the DB effects and retur the first result object.

Returns: String


License

Distributed under the MIT License. See LICENSE for more information.

Contact

Fabiano Taioli - ftaioli@gmail.com

Project Link: https://github.com/FbN/iodio