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

@fairscript/interact

v0.17.0

Published

A database interaction library for Node.js/JavaScript/TypeScript that uses code reflection to maximize type safety and minimize friction. Supports PostgreSQL and SQLite.

Downloads

32

Readme

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 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

Getting 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 = 1

Table 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
        [...]
    })