listerine
v1.0.1
Published
A powerful, type-safe in-memory query engine for JavaScript/TypeScript arrays. Listerine provides MongoDB-like query syntax for filtering, searching, and retrieving data from arrays of objects.
Readme
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
$andand$oroperations - 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 listerineQuick 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
