0.17.0 • Published 5 years ago
@fairscript/interact v0.17.0
Interact
A database interaction library for node.js/JavaScript/TypeScript that uses code reflection to maximize type safety and minimize friction. Supports PostgreSQL and SQLite.
Installation
Interact can be installed from npm:
npm install interactThere is one additional module for each of the three supported databases:
# Required for Postgres support
npm install interact-with-postgres
# Required for SQLite support
npm install interact-with-sqliteGetting started
Step 1: Define a type
interface Employee {
id: number,
firstName: string,
lastName: string,
title: string,
salary: number,
departmentId: string
fulltime: boolean
}Step 2: Define a table
import { defineTable } from '@fairscript/interact'
const employees = defineTable<Employee>(
'employees',
{
id: 'number',
firstName: 'string',
lastName: 'string',
title: 'string',
salary: 'integer',
departmentId: 'string',
fulltime: 'boolean'
})defineTable is a generic function that expects two arguments: the database table name and a record specifying the column types for the specified type.
Step 3) Create a database context and start running queries
const dbContext = createSqliteContext(filename)
const query = employees
.filter(e => e.id === 1)
.map(e => ({ first: e.firstName, last: e.lastName }))
const namesOfEmployees = dbContext.run(query)This generates the following SQL query:
SELECT t1.first_name AS first, t1.last_name AS last
FROM employees t1
WHERE t1.id = 1Table definition
const employees = defineTable<Employee>(
'employees',
{
id: 'integer',
firstName: 'string',
lastName: 'string',
title: 'string',
salary: 'integer',
departmentId: 'integer',
fulltime: 'boolean'
})
const departments = defineTable<Department>(
'departments',
{
id: 'integer',
name: 'string',
companyId: 'integer'
})
const companies = defineTable<Company>(
'companies',
{
id: 'integer',
name: 'string'
})Supported databases
In-memory SQLite
const context = createSqliteInMemoryContext()On-disk SQLite
const context = createSqliteOnDiskContext(filename)Postgres
import {Client} from 'pg'
const pg = new Client(...)
await pg.connect()
const context = createPostgresContext(pg)
await pg.end()Selection
Single column
employees.get(e => e.id)Single row
employees
.filter(e => e.id === 1)
.single()Map over rows
employees
.map(e => ({ firstName: e.firstName, lastName: e.lastName }))Single table
employees.select()Limited number of rows
employees
.select()
.limit(n)Limited number of rows, with an offset
employees
.select()
.limit(m)
.offset(n)Distinct rows
employees
.select()
.distinct()Aggregation
Number of rows
employees.count()Minimum value in a column
employees.min(e => e.salary)Maximum value in a column
employees.max(e => e.salary)Sum of values in a column
employees.sum(e => e.salary)Average column value
employees.sum(e => e.average)Selecting multiple aggregations
employees
.aggregate((e, count) => ({
lowestSalary: e.salary.min(),
highestSalary: e.salary.max(),
totalSalaries: e.salary.sum(),
averageSalary: e.salary.average(),
numberOfEmployees: count()
}))Aggregating groups
employees
.groupBy(e => e.departmentId)
.aggregate((key, e, count) => ({
lowestSalary: e.salary.min(),
highestSalary: e.salary.max(),
totalSalaries: e.salary.sum(),
averageSalary: e.salary.average(),
employeesInDepartment: count()
}))Filtering
Comparison
employees.filter(e => e.id == 1)
employees.filter(e => e.id === 1)
employees.filter(e => e.id != 1)
employees.filter(e => e.id !== 1)
employees.filter(e => e.salary > 10000)
employees.filter(e => e.salary >= 10000)
employees.filter(e => e.salary < 10000)
employees.filter(e => e.salary <= 10000)Evaluating a Boolean column
employees.filter(e => e.fulltime)
employees.filter(e => !e.fulltime)Conjunction
employees.filter(e => e.firstName === 'John' && e.lastName === 'Doe')
employees
.filter(e => e.firstName === 'John')
.filter(e => e.lastName === 'Doe')Disjunction
employees.filter(e => e.firstName === 'Jim' && e.firstName === 'James')Conjunction of disjunctions
employees.filter(e => (e.firstName === 'John' || e.firstName === 'Richard') && (e.firstName === 'Doe' || e.firstName === 'Roe'))Disjunction of conjunctions
employees.filter(e => (e.firstName = 'John' && e.firstName = 'Doe') || (e.firstName = 'Richard' || e.firstName = 'Roe'))User-provided value
employees.filter(1, (id, e) => e.id === 1)User-provided object
employees
.filter(
{ firstName: 'John', lastName: 'Doe' },
(search, e) => e.firstName === search.firstName, e.lastName === search.lastName)
)Sorting features
Ascending order
employees
.sortBy(e => e.id)
.select()Descending order
employees
.sortDescendinglyBy(e => e.salary)
.select()Multiple orders
employees
.sortBy(e => e.departmentId)
.thenDescendinglyBy(e => e.salary)
.select()Joins
Joining tables
employees
.join(departments, e => e.departmentId, d => d.id)
.join(departments, e => e.companyId, c => c.id)Column from a joined table
employees
.join(departments, e => e.departmentId, d => d.id)
.get((e, d) => d.name)Map over rows
employees
.join(departments, e => e.departmentId, d => d.id)
.get((e, d) => {
firstName: e.firstName,
lastName: e.lastName,
department: d.name
})Selecting multiple tables
employees
.join(departments, e => e.departmentId, d => d.id)
.join(companies, d => d.companyId, c => c.id)
.select('employee', 'department', 'company')Subqueries
Number of rows
employees.map(
employees,
(subtable, e) => ({
id: e.id,
departmentSize: subtable
.filter(se => se.departmentId === e.departmentId)
.count()
}))Minimum value in a column
employees.map(
employees,
(subtable, e) => ({
id: e.id,
lowestSalaryInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.min(se => se.salary)
}))Maximum value in a column
employees.map(
employees,
(subtable, e) => ({
id: e.id,
highestSalaryInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.max(se => se.salary)
}))Sum of values in a column
employees.map(
employees,
(subtable, e) => ({
id: e.id,
totalSalariesInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.sum(se => se.salary)
}))Average column value
employees.map(
employees,
(subtable, e) => ({
id: e.id,
averageSalaryInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.average(se => se.salary)
}))Parallel queries
const promiseOfResults: Promise = context
.parallelRun({
numberOfEmployees: employees.count(),
numberOfDepartments: departments.count(),
numberOfCompanies: companies.count()
})
.then(res => {
{ numberOfEmployees, numberOfDepartments, numberOfCompanies } = res
[...]
})