DISCONTINUED: please use Futurity
Implements Fantasy Land:
Functor, Bifunctor,
Apply, Applicative, Chain,
Monad
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.
Iodio is implemented in a few lines of code that mix up some good libraries:
- Add Iodio to your project
yarn add iodio
- If not already in your project add peer dependencies
yarn add knex
yarn add fluture
- Do not forget your DB connection layer, ex. sqlite3
yarn add sqlite3
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'
]
Iodio base constructor. It's rare to call it directly, you can call other more confortable constructors: lift, resolve, merge. (pPred, qbR, fR)
-
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
Contruct a new IodioInstance setting knex db connection object and an array of params to pass to knex to get the QueryBuilder.
-
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
Contruct an IodioInstance resolved to a fixed value. Working on QueryBuilder of the resulting monad currently not supported.
v: any
The resolution value.
Returns: IodioInstance
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.
-
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
Params Map. Function to transform the query params object binded to the monad.
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
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).
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
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).
pred: (f: FutureInstance => f: FutureInstance)
Returns: IodioInstance
It's like calling qMap and Map at the same time. Let you work on the two side of the Bifunctor.
qPred: ((qb: QueryBuilder, p: Object) => QueryBuilder)
fPred: (f: FutureInstance => f: FutureInstance)
Returns: IodioInstance
Like map but the supplied function must return an IodioInstance. The computation will continue with the new IodioInstance.
pred
Function returning an IodioInstance
Returns: IodioInstance
Call the function wrapped as future value in the supplied IodioInstance passing the self future value as parameter.
iodioFuntion: IodioInstance
An Iodio Instance that wraps a function as future value.
Returns: IodioInstance
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
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.
left
Error predicate.right
Error predicate.
Returns: Cancel
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
Shortcut for testing purpouse. Make the IodioInstance collapse, exectuing the DB effects and retur the first result object.
Returns: String
Distributed under the MIT License. See LICENSE
for more information.
Fabiano Taioli - ftaioli@gmail.com
Project Link: https://github.com/FbN/iodio