A database interaction library for node.js/JavaScript/TypeScript that uses code reflection to maximize type safety and minimize friction. Supports PostgreSQL and SQLite.
Interact can be installed from npm:
npm install interact
There 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-sqlite
interface Employee {
id: number,
firstName: string,
lastName: string,
title: string,
salary: number,
departmentId: string
fulltime: boolean
}
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.
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 = 1
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'
})
const context = createSqliteInMemoryContext()
const context = createSqliteOnDiskContext(filename)
import {Client} from 'pg'
const pg = new Client(...)
await pg.connect()
const context = createPostgresContext(pg)
await pg.end()
employees.get(e => e.id)
employees
.filter(e => e.id === 1)
.single()
employees
.map(e => ({ firstName: e.firstName, lastName: e.lastName }))
employees.select()
employees
.select()
.limit(n)
employees
.select()
.limit(m)
.offset(n)
employees
.select()
.distinct()
employees.count()
employees.min(e => e.salary)
employees.max(e => e.salary)
employees.sum(e => e.salary)
employees.sum(e => e.average)
employees
.aggregate((e, count) => ({
lowestSalary: e.salary.min(),
highestSalary: e.salary.max(),
totalSalaries: e.salary.sum(),
averageSalary: e.salary.average(),
numberOfEmployees: count()
}))
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()
}))
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)
employees.filter(e => e.fulltime)
employees.filter(e => !e.fulltime)
employees.filter(e => e.firstName === 'John' && e.lastName === 'Doe')
employees
.filter(e => e.firstName === 'John')
.filter(e => e.lastName === 'Doe')
employees.filter(e => e.firstName === 'Jim' && e.firstName === 'James')
employees.filter(e => (e.firstName === 'John' || e.firstName === 'Richard') && (e.firstName === 'Doe' || e.firstName === 'Roe'))
employees.filter(e => (e.firstName = 'John' && e.firstName = 'Doe') || (e.firstName = 'Richard' || e.firstName = 'Roe'))
employees.filter(1, (id, e) => e.id === 1)
employees
.filter(
{ firstName: 'John', lastName: 'Doe' },
(search, e) => e.firstName === search.firstName, e.lastName === search.lastName)
)
employees
.sortBy(e => e.id)
.select()
employees
.sortDescendinglyBy(e => e.salary)
.select()
employees
.sortBy(e => e.departmentId)
.thenDescendinglyBy(e => e.salary)
.select()
employees
.join(departments, e => e.departmentId, d => d.id)
.join(departments, e => e.companyId, c => c.id)
employees
.join(departments, e => e.departmentId, d => d.id)
.get((e, d) => d.name)
employees
.join(departments, e => e.departmentId, d => d.id)
.get((e, d) => {
firstName: e.firstName,
lastName: e.lastName,
department: d.name
})
employees
.join(departments, e => e.departmentId, d => d.id)
.join(companies, d => d.companyId, c => c.id)
.select('employee', 'department', 'company')
employees.map(
employees,
(subtable, e) => ({
id: e.id,
departmentSize: subtable
.filter(se => se.departmentId === e.departmentId)
.count()
}))
employees.map(
employees,
(subtable, e) => ({
id: e.id,
lowestSalaryInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.min(se => se.salary)
}))
employees.map(
employees,
(subtable, e) => ({
id: e.id,
highestSalaryInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.max(se => se.salary)
}))
employees.map(
employees,
(subtable, e) => ({
id: e.id,
totalSalariesInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.sum(se => se.salary)
}))
employees.map(
employees,
(subtable, e) => ({
id: e.id,
averageSalaryInDepartment: subtable
.filter(se => se.departmentId === e.departmentId)
.average(se => se.salary)
}))
const promiseOfResults: Promise = context
.parallelRun({
numberOfEmployees: employees.count(),
numberOfDepartments: departments.count(),
numberOfCompanies: companies.count()
})
.then(res => {
{ numberOfEmployees, numberOfDepartments, numberOfCompanies } = res
[...]
})