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 🙏

© 2026 – Pkg Stats / Ryan Hefner

safe-sift

v1.0.11

Published

Type-safe wrapper for sift.js providing compile-time query validation

Readme

Safe Sift

A type-safe wrapper for sift.js that provides compile-time query validation and autocompletion for MongoDB-style queries.

Features

  • 🛡️ Type Safety: Compile-time validation of queries against your data schema
  • 🔍 IntelliSense: Full autocompletion support for query operators and field names
  • 🎯 Deep Object Support: Type-safe querying of nested properties
  • 📦 Zero Dependencies: Only depends on sift.js
  • 🚀 Performance: No runtime overhead beyond sift.js itself

Installation

npm install safe-sift sift

Usage

Fluent Builder Pattern (Recommended)

import { query } from 'safe-sift';

interface User {
  id: number;
  name: string;
  age: number;
  isActive: boolean;
  tags: string[];
  profile: {
    location: string;
    preferences: { theme: 'light' | 'dark' };
  };
}

const users: User[] = [/* ... */];

// Fluent, type-safe query building
const result = query<User>()
  .where('isActive').equals(true)
  .and('age').between(25, 40)
  .and('tags').contains('developer')
  .and('profile.preferences.theme').equals('dark')
  .execute()
  .filter(users);

// Or build query object for later use
const queryObj = query<User>()
  .where('name').regex(/john/i)
  .or('email').matches(/@company\.com$/)
  .build(); // Returns SafeSiftQuery<User>

Basic Example

import { SafeSift, safeSift } from 'safe-sift';

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
  tags: string[];
}

const users: User[] = [
  { id: 1, name: 'John', email: '[email protected]', age: 30, isActive: true, tags: ['admin'] },
  { id: 2, name: 'Jane', email: '[email protected]', age: 25, isActive: false, tags: ['user'] },
];

// Class-based approach
const query = new SafeSift<User>({ 
  isActive: true, 
  age: { $gte: 25 } 
});

const activeUsers = query.filter(users);
console.log(activeUsers); // [{ id: 1, name: 'John', ... }]

// Functional approach
const { filter, find, test } = safeSift<User>({ 
  tags: { $in: ['admin', 'moderator'] } 
});

const adminUsers = filter(users);
const firstAdmin = find(users);
const isAdmin = test(users[0]);

Nested Object Queries

interface User {
  profile: {
    bio: string;
    location: string;
    preferences: {
      theme: 'light' | 'dark';
      notifications: boolean;
    };
  };
}

// Type-safe nested property access
const darkThemeUsers = new SafeSift<User>({
  'profile.preferences.theme': 'dark'
});

// Compile-time error for invalid paths
const invalid = new SafeSift<User>({
  'profile.invalid.path': 'value' // ❌ TypeScript error
});

Array Operations

interface Order {
  items: { name: string; price: number; category: string }[];
}

// Array element matching
const expensiveElectronics = new SafeSift<Order>({
  items: { 
    $elemMatch: { 
      category: 'electronics', 
      price: { $gt: 500 } 
    } 
  }
});

// Array contains all elements
const multiCategoryOrders = new SafeSift<Order>({
  'items.category': { $all: ['electronics', 'books'] }
});

Logical Operators

// Complex logical queries
const complexQuery = new SafeSift<User>({
  $and: [
    { age: { $gte: 18 } },
    {
      $or: [
        { isActive: true },
        { tags: { $in: ['vip', 'premium'] } }
      ]
    }
  ]
});

Builder Pattern Methods

QueryBuilder

// Start building a query
query<T>(): QueryBuilder<T>

// Field selection
where<K extends DeepKeyOf<T>>(field: K): FieldBuilder<T, K>
and<K extends DeepKeyOf<T>>(field: K): FieldBuilder<T, K>
or<K extends DeepKeyOf<T>>(field: K): FieldBuilder<T, K>
not(): QueryBuilder<T>

// Finalization
build(): SafeSiftQuery<T>
execute(): SafeSift<T>

FieldBuilder Operations

// Equality
equals(value): QueryBuilder<T>
eq(value): QueryBuilder<T>
notEquals(value): QueryBuilder<T>
ne(value): QueryBuilder<T>

// Comparisons (numbers, strings, dates)
greaterThan(value): QueryBuilder<T>
gt(value): QueryBuilder<T>
greaterThanOrEqual(value): QueryBuilder<T>
gte(value): QueryBuilder<T>
lessThan(value): QueryBuilder<T>
lt(value): QueryBuilder<T>
lessThanOrEqual(value): QueryBuilder<T>
lte(value): QueryBuilder<T>
between(min, max): QueryBuilder<T>

// Arrays and collections
in(values): QueryBuilder<T>
notIn(values): QueryBuilder<T>
nin(values): QueryBuilder<T>
contains(value): QueryBuilder<T>  // Array element matching
all(values): QueryBuilder<T>      // Array contains all
size(value): QueryBuilder<T>      // Array size
elemMatch(query): QueryBuilder<T> // Array element query

// String operations
regex(pattern): QueryBuilder<T>
matches(pattern): QueryBuilder<T>

// Existence
exists(value?): QueryBuilder<T>

API Reference

SafeSift Class

class SafeSift<T> {
  constructor(query: SafeSiftQuery<T>)
  
  test(obj: T): boolean
  filter(array: T[]): T[]
  find(array: T[]): T | undefined
  findIndex(array: T[]): number
  some(array: T[]): boolean
  every(array: T[]): boolean
  count(array: T[]): number
}

Factory Functions

// Fluent query builder (recommended)
function query<T>(): QueryBuilder<T>

// Create SafeSift instance
function createQuery<T>(query: SafeSiftQuery<T>): SafeSift<T>

// Get query functions directly
function safeSift<T>(query: SafeSiftQuery<T>): {
  test: (obj: T) => boolean;
  filter: (array: T[]) => T[];
  find: (array: T[]) => T | undefined;
  findIndex: (array: T[]) => number;
  some: (array: T[]) => boolean;
  every: (array: T[]) => boolean;
  count: (array: T[]) => number;
}

Utility Functions

Safe Sift also exports several utility functions for advanced use cases:

// Query value extraction
import { getFilterValue, getFilterOps } from 'safe-sift';

// Get a specific operator value or the raw operator bag from a query
const value = getFilterValue(
  { age: { $gte: 18, $lte: 65 } }, 
  'age', 
  '$gte'  // Optional: specific operator
);
console.log(value); // 18

// Get all operator conditions for a field
const ops = getFilterOps({ age: { $gte: 18, $lte: 65 } }, 'age');
console.log(ops); // { $gte: 18, $lte: 65 }

// Query normalization and analysis
import { 
  normalizeQuery, 
  bagFromPreds, 
  isOperatorKey,
  mergeOpsBags,
  normalizeEquality,
  areQueriesEqual 
} from 'safe-sift';

// Normalize a query into predicates
const normalized = normalizeQuery({ 
  age: { $gte: 18 }, 
  name: 'John',
  $or: [{ isActive: true }, { tags: 'admin' }] 
});

// Check if two queries are equivalent
const query1 = { age: { $gte: 18 } };
const query2 = { age: { $gte: 18 } };
const areEqual = areQueriesEqual(query1, query2); // true

// Check if a key is a MongoDB operator
const isOp = isOperatorKey('$gte'); // true
const isField = isOperatorKey('age'); // false

// Merge operator bags
const merged = mergeOpsBags(
  { $gte: 18 }, 
  { $lte: 65 }
); // { $gte: 18, $lte: 65 }

// Normalize equality values
const normalized = normalizeEquality('value'); // { $eq: 'value' }

// Convert predicates to operator bag
const bag = bagFromPreds([
  { path: 'age', op: '$gte', value: 18 },
  { path: 'age', op: '$lte', value: 65 }
]); // { $gte: 18, $lte: 65 }

Supported Operators

Comparison Operators

  • $eq - Equal to
  • $ne - Not equal to
  • $gt - Greater than
  • $gte - Greater than or equal
  • $lt - Less than
  • $lte - Less than or equal
  • $in - In array
  • $nin - Not in array
  • $regex - Regular expression match
  • $exists - Field exists
  • $type - Field type check
  • $size - Array size

Logical Operators

  • $and - Logical AND
  • $or - Logical OR
  • $nor - Logical NOR
  • $not - Logical NOT

Array Operators

  • $all - Array contains all elements
  • $elemMatch - Array element matches query

Type Safety Benefits

Safe Sift prevents common query mistakes at compile time:

interface User {
  name: string;
  age: number;
  tags: string[];
}

// ❌ Compile-time errors with traditional approach
new SafeSift<User>({ 
  invalidField: 'value'           // Property doesn't exist
});

// ❌ These would be caught at compile time
query<User>()
  .where('invalidField')          // ❌ Property doesn't exist
  .where('age').regex(/pattern/)  // ❌ Wrong operator for number
  .where('name').size(5)          // ❌ $size only works on arrays

// ✅ Fluent builder with full type safety
query<User>()
  .where('name').regex(/john/i)           // ✅ String regex
  .and('age').between(18, 65)             // ✅ Number range  
  .and('tags').contains('developer')      // ✅ Array contains
  .and('tags').size(3)                    // ✅ Array size

Builder Pattern Benefits

  • IntelliSense: Full autocompletion for field names and operations
  • Method Chaining: Readable, fluent API similar to popular libraries
  • Type Safety: Compile-time validation of all operations
  • Flexibility: Build queries step by step or all at once

Examples

See the __tests__ directory for comprehensive examples of all supported query types and operators.

Contributing

Contributions are welcome! Please read the contributing guidelines and ensure all tests pass.

License

MIT