Skip to content

tasteee/listerine

Repository files navigation

listerine

listerine is a powerful, type-safe, in-memory query engine with Mongo-ish syntax for performing complex (or simple!) queries on arrays of objects.

Features

  • MongoDB-like Query Syntax: Filters and stuff
  • Type Safety: Full TypeScript support with generic types
  • Logical Operators: Support for $and and $or operations
  • Nested Object Queries: Query deeply nested object properties
  • Array Operations: Comprehensive array filtering and matching
  • String Operations: Text searching, pattern matching, and length checks
  • Numeric Comparisons: Range / equality checks, mathematical operations
  • Existence Checks: Test for null, undefined, empty values

Installation

npm install listerine

Quick Start

import { listerine } from 'listerine'

type UserT = {
  id: string,
  name: string,
  age: number,
  tags: string[]
}

const hannah = { id: '1', name: 'hannah', age: 25, tags: ['admin', 'dev'] }
const lily = { id: '2', name: 'lily', age: 18, tags: ['guest'] }
const sailor = { id: '3', name: 'sailor', age: 32, tags: ['guest'] }
const userList = [hannah, lily, sailor]
const userCollection = listerine<UserT>(userList)

const query =  { age$: { $isGreaterThan: 20 } }
const adults = userCollection.find(query)
expect(adults).toEqual([hannah, sailor])

const query = { tags$: { $contains: 'guest' } }
const guests = userCollection.find(query)
expect(guests).toEqual([lily, sailor])

const members = userCollection.findById(['1', '2'])
expect(members).toEqual([hannah, lily])

const member = userCollection.findById('1')
expect(member).toEqual(hannah)

const member = userCollection.findOne({ name$: { $contains: 'i' } })
expect(member).toEqual(lily)

API Reference

listerine<DataT>(dataList: DataT[])

Creates a new listerine collection with the provided array of objects.

const collection = listerine<UserT>(userList)
expect(collection).toHaveProperty('find')
expect(collection).toHaveProperty('findById')
expect(collection).toHaveProperty('findByIds')

NOTE: listerine will apply an id property to any object in dataList that does not already have one.

collection.find(query: QueryT)

Finds and returns an array of all documents matching the query criteria.

const collection = listerine<UserT>(userList)
const results = collection.find({ isActive: true })
expect(Array.isArray(results)).toEqual(true)

collection.findById(id: string)

Finds and returns the document that has the provided id. If no document has the provided id, returns null.

const collection = listerine<UserT>(userList)
const result = collection.findById('123')

collection.findByIds(ids: string[])

Finds and returns any documents with an id found in the provided ids. If no documents match the provided ids, an empty array.

const collection = listerine<UserT>(userList)
const result = collection.findById('123')
expect(Array.isArray(results)).toEqual(true)

Query Syntax

Basic Equality

// Direct equality (implicit $equals)
list.find({ name: 'hannah' })

// Explicit equality
list.find({ name$: { $equals: 'hannah' } })

// Not equal
list.find({ name$: { $doesNotEqual: 'hannah' } })

Numeric Comparisons

// Greater than
list.find({ age$: { $isGreaterThan: 25 } })

// Less than
list.find({ age$: { $isLessThan: 30 } })

// Greater than or equal to
list.find({ age$: { $isGreaterThanOrEqualTo: 25 } })

// Less than or equal to
list.find({ age$: { $isLessThanOrEqualTo: 30 } })

// Between (inclusive)
list.find({ age$: { $isBetween:  } })

// Not between
list.find({ age$: { $isNotBetween:  } })

Array Operations

// Contains a value
list.find({ tags$: { $contains: 'admin' } })

// Contains all values
list.find({ tags$: { $containsAll: ['admin', 'dev'] } })

// Contains some values
list.find({ tags$: { $containsSome: ['admin', 'user'] } })

// Does not contain
list.find({ tags$: { $doesNotContain: 'admin' } })

// Is one of (value in array)
list.find({ role$: { $isOneOf: ['admin', 'moderator'] } })

// Is not one of
list.find({ role$: { $isNotOneOf: ['banned', 'suspended'] } })

// Is in (array is subset)
list.find({ permissions$: { $isIn: ['read', 'write', 'delete'] } })

// Is subset of
list.find({ tags$: { $isSubsetOf: ['admin', 'user', 'guest'] } })

// Is superset of
list.find({ permissions$: { $isSupersetOf: ['read'] } })

String Operations

// Contains substring
list.find({ name$: { $contains: 'Ali' } })

// Starts with
list.find({ name$: { $startsWith: 'A' } })

// Ends with
list.find({ email$: { $endsWith: '@example.com' } })

// Does not start with
list.find({ name$: { $doesNotStartWith: 'B' } })

// Does not end with
list.find({ email$: { $doesNotEndWith: '@spam.com' } })

// Length comparisons
list.find({ name$: { $isLongerThan: 5 } })
list.find({ name$: { $isShorterThan: 10 } })
list.find({ name$: { $isNotLongerThan: 15 } })
list.find({ name$: { $isNotShorterThan: 3 } })

Existence and Emptiness

// Field exists
list.find({ email$: { $exists: true } })

// Field does not exist
list.find({ email$: { $exists: false } })
list.find({ email$: { $doesNotExist: true } })

// Field is empty (null, undefined, '', [], {})
list.find({ description$: { $isEmpty: true } })

// Field is not empty
list.find({ description$: { $isNotEmpty: true } })

Logical Operators

// AND operation (default behavior)
list.find({
  age$: { $isGreaterThan: 25 },
  name$: { $startsWith: 'A' },
})

// Explicit AND
list.find({
  $and: [{ age$: { $isGreaterThan: 25 } }, { name$: { $startsWith: 'A' } }],
})

// OR operation
list.find({
  $or: [{ age$: { $isLessThan: 25 } }, { tags$: { $contains: 'admin' } }],
})

// Complex nested logic
list.find({
  $and: [
    {
      $or: [{ age$: { $isLessThan: 25 } }, { age$: { $isGreaterThan: 35 } }],
    },
    { tags$: { $contains: 'active' } },
  ],
})

Nested Object Queries

const users = [
  {
    id: '1',
    profile: {
      personal: {
        name: 'hannah',
        age: 25,
      },
      settings: {
        theme: 'dark',
      },
    },
  },
]

const list = listerine(users)

// Nested query
list.find({
  profile: {
    personal: {
      name: 'hannah',
      age$: { $isGreaterThan: 25 },
    },
  },
})

// Or...
list.find({
  'profile.personal.name': 'hannah',
  'profile.personal.age$': { $isGreaterThan: 25 },
})

Advanced Examples

User Management System

type UserT = {
  id: string
  username: string
  email: string
  age: number
  roles: string[]
  isActive: boolean
  lastLogin?: Date
  profile: {
    firstName: string
    lastName: string
    bio?: string
  }
}

// πŸ’­ Imagine there are more users that just the one.
const users: UserT[] = [
  {
    id: '1',
    username: 'alice_admin',
    email: '[email protected]',
    age: 25,
    roles: ['admin', 'user'],
    isActive: true,
    profile: {
      firstName: 'hannah',
      lastName: 'Johnson',
      bio: 'System administrator',
    },
  }
]

const usersCollection = listerine<UserT>(memberList)

// Find active admin users
const activeAdmins = usersCollection.find({
  isActive: true,
  roles$: { $contains: 'admin' },
})

// Find users with incomplete profiles
const incompleteProfiles = usersCollection.find({
  'profile.bio$': { $doesNotExist: true }
})

// Find young active users or admins
const targetUsers = usersCollection.find({
  $or: [
    { roles$: { $contains: 'admin' } },
    {
      isActive: true,
      age$: { $isLessThan: 25 }
    },
  ],
})

E-commerce Product Search

type ProductT =  {
  id: string
  name: string
  price: number
  category: string
  tags: string[]
  inStock: boolean
  rating: number
  description: string
}

// πŸ’­ Imagine there are more products that just the one.
const products: ProductT[] = [
  {
    id: '1',
    name: 'Wireless Headphones',
    price: 99.99,
    category: 'Electronics',
    tags: ['wireless', 'audio', 'bluetooth'],
    inStock: true,
    rating: 4.5,
    description: 'High-quality wireless headphones with noise cancellation',
  },
]

const productsCollection = listerine<ProductT>(products)

// Find affordable electronics in stock
const affordableElectronics = productsCollection.find({
  category: 'Electronics',
  price$: { $isLessThanOrEqualTo: 100 },
  inStock: true,
})

// Find highly rated products with specific features
const premiumProducts = productsCollection.find({
  rating$: { $isGreaterThanOrEqualTo: 4.0 },

  $or: [
    { tags$: { $contains: 'premium' } },
    { price$: { $isGreaterThan: 200 } }
  ],
})

// Search products by description keywords
const results = productsCollection.find({
  $or: [
    { name$: { $contains: 'wireless' } },
    { description$: { $contains: 'wireless' } },
    { tags$: { $contains: 'wireless' } }
  ],
})

TypeScript Support

listerine is built with TypeScript and provides full type safety:

type DataT = {
  id: string
  name: string
  count: number
}

const data: DataT[] = [...]
const list = listerine<DataT>(data) // Fully typed

// TypeScript will enforce correct property names and types
const results = list.find({
  name$: { $contains: 'test' }, // βœ… Valid
  count$: { $isGreaterThan: 5 }, // βœ… Valid
  // invalid$: { $contains: 'test' } // ❌ TypeScript error
})

Contributing

Contributions are welcome!
No rules or templates -- just have fun!

License

MIT License

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published