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

@hf-chimera/query-builder

v0.2.3

Published

Cross-end reactivity API - Query builder

Readme

ChimeraQueryBuilder

A type-safe query builder for constructing filters and ordering rules for Chimera Store collections. ChimeraQueryBuilder provides a fluent API for building complex queries with support for filtering, ordering, and nested grouping.

Features

  • Type-Safe: Full TypeScript support with automatic type inference
  • Fluent API: Chainable methods for building queries
  • Complex Filtering: Support for operators, negations, and grouped conditions
  • Ordering: Flexible sorting with null handling options
  • Nested Groups: Create complex logical groupings with AND/OR/NOT

Installation

# Install both the core store and query builder
npm install @hf-chimera/store @hf-chimera/query-builder

# Or with yarn
yarn add @hf-chimera/store @hf-chimera/query-builder

# Or with pnpm
pnpm add @hf-chimera/store @hf-chimera/query-builder

Setting Up Your Store

Before using ChimeraQueryBuilder, you need to create entity stores. Here's a complete example:

import { createChimeraEntityStore } from "@hf-chimera/store";

// Define your entity types
type Customer = {
  id: number;
  name: string;
  email: string;
  phone: string;
  createdAt: Date;
};

type Order = {
  id: number;
  customerId: number;
  productName: string;
  quantity: number;
  totalAmount: number;
  status: "pending" | "completed" | "cancelled";
  createdAt: Date;
};

// Create entity stores
export const customerStore = createChimeraEntityStore<"customer", Customer>({
  name: "customer",
  idGetter: "id",
  async collectionFetcher(params, requestParams) {
    const response = await fetch(`/api/customers`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ filter: params.filter, order: params.order }),
      signal: requestParams.signal,
    });
    return { data: await response.json() };
  },
  async itemCreator(item, requestParams) {
    const response = await fetch(`/api/customers`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(item),
      signal: requestParams.signal,
    });
    return { data: await response.json() };
  },
  async itemUpdater(item, requestParams) {
    const response = await fetch(`/api/customers/${item.id}`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(item),
      signal: requestParams.signal,
    });
    return { data: await response.json() };
  },
  async itemDeleter(id, requestParams) {
    await fetch(`/api/customers/${id}`, {
      method: "DELETE",
      signal: requestParams.signal,
    });
    return { result: { id, success: true } };
  },
  async itemFetcher(params, requestParams) {
    const response = await fetch(`/api/customers/${params.id}`, {
      signal: requestParams.signal,
    });
    return { data: await response.json() };
  },
});

export const orderStore = createChimeraEntityStore<"order", Order>({
  name: "order",
  idGetter: "id",
  // ... similar CRUD operations
});

Basic Usage

import { ChimeraQueryBuilder } from "@hf-chimera/query-builder";
import { customerStore } from "./store";

// Create a query builder instance
const query = new ChimeraQueryBuilder<typeof customerStore, Customer, any>();

// Build a simple query
query
  .where("name", "eq", "John")
  .where("age", "gte", 18)
  .orderBy("createdAt", true); // true = descending

// Build the query descriptor
const descriptor = query.build();
// Returns: { filter: {...}, order: [...] }

// Use with entity store
const customers = customerStore.getCollection(descriptor);

API Reference

orderBy(key, desc?, nulls?)

Adds an ordering rule to the query.

Parameters:

  • key: ChimeraPropertyGetter<Entity> | (keyof Entity & string) - The property to order by (can be a property name string or a property getter function)
  • desc: boolean (default: false) - Whether to sort in descending order
  • nulls: ChimeraOrderNulls (default: ChimeraOrderNulls.Last) - How to handle null values (Last or First)

Returns: this - Returns the builder instance for chaining

Example:

query
  .orderBy("name") // Ascending
  .orderBy("age", true) // Descending
  .orderBy("createdAt", false, ChimeraOrderNulls.First); // Nulls first

where(value, op, test)

Adds a filter condition to the query. Multiple where calls are combined with AND logic by default.

Parameters:

  • value: ChimeraPropertyGetter<Entity> | (KeysOfType<Entity, T> & string) - The property to filter on (property name or getter function)
  • op: Op extends keyof OperatorsMap & string - The operator to use (e.g., 'eq', 'gte', 'in', etc.)
  • test: Parameters<OperatorsMap[Op]>[1] - The value to compare against (type depends on the operator)

Returns: this - Returns the builder instance for chaining

Example:

query
  .where("status", "eq", "active")
  .where("age", "gte", 18)
  .where("tags", "in", ["javascript", "typescript"]);

whereNot(value, op, test)

Adds a negated filter condition (NOT condition) to the query.

Parameters:

  • value: Same as where()
  • op: Same as where()
  • test: Same as where()

Returns: this - Returns the builder instance for chaining

Example:

query.where("status", "eq", "active").whereNot("deleted", "eq", true); // NOT deleted = true

group(conjunction, builder)

Creates a grouped condition with a specified logical conjunction. Useful for creating complex logical expressions with AND, OR, or NOT groups.

Parameters:

  • conjunction: ChimeraConjunctionType - The logical operator: 'and', 'or', or 'not'
  • builder: QueryBuilderCreator<Store, Entity, OperatorsMap> - A callback function that receives a new builder instance for building the nested query

Returns: this - Returns the builder instance for chaining

Example:

// Create an OR group: (status = 'active' OR status = 'pending')
query.group("or", (q) => {
  q.where("status", "eq", "active").where("status", "eq", "pending");
});

// Create a NOT group: NOT (age < 18 OR deleted = true)
query.group("not", (q) => {
  q.group("or", (nested) => {
    nested.where("age", "lt", 18).where("deleted", "eq", true);
  });
});

build()

Builds and returns the final query descriptor object.

Returns:

{
  filter: ChimeraFilterDescriptor<OperatorsMap, Entity> | null;
  order: (ChimeraOrderDescriptor < Entity > []) | null;
}
  • filter: The filter descriptor, or null if no filters were added
  • order: An array of order descriptors, or null if no ordering was specified

Example:

const { filter, order } = query.build();

// Use with store
const collection = store.from("customer").getCollection({ filter, order });

Examples

Note: The following examples use generic type parameters like Store, User, and OperatorsMap. See Setting Up Your Store section above for a complete store setup example.

Simple Filtering

const query = new ChimeraQueryBuilder<Store, User, OperatorsMap>();

query
  .where("email", "eq", "[email protected]")
  .where("active", "eq", true)
  .orderBy("createdAt", true);

const { filter, order } = query.build();

Complex Logical Groups

const query = new ChimeraQueryBuilder<Store, Post, OperatorsMap>();

query
  // Main AND condition
  .where("published", "eq", true)
  .where("createdAt", "gte", new Date("2024-01-01"))

  // OR group: (authorId = '123' OR authorId = '456')
  .group("or", (q) => {
    q.where("authorId", "eq", "123").where("authorId", "eq", "456");
  })

  // NOT group: NOT (tags contains 'draft')
  .group("not", (q) => {
    q.where("tags", "contains", "draft");
  })

  .orderBy("createdAt", true)
  .orderBy("title"); // Secondary sort

const { filter, order } = query.build();

Multiple Order Rules

const query = new ChimeraQueryBuilder<Store, Product, OperatorsMap>();

query
  .where("inStock", "eq", true)
  .orderBy("category") // Primary sort: category ascending
  .orderBy("price", false) // Secondary sort: price ascending
  .orderBy("rating", true) // Tertiary sort: rating descending
  .orderBy("name"); // Final sort: name ascending

const { filter, order } = query.build();

Combining whereNot and Groups

const query = new ChimeraQueryBuilder<Store, Article, OperatorsMap>();

query
  .where("published", "eq", true)
  .whereNot("archived", "eq", true)

  // Either in featured category OR has high views
  .group("or", (q) => {
    q.where("category", "eq", "featured").where("views", "gte", 1000);
  })

  // But NOT in any of these tags
  .group("not", (q) => {
    q.where("tags", "in", ["spam", "deprecated"]);
  })

  .orderBy("publishedAt", true);

const { filter, order } = query.build();

Empty Query

const query = new ChimeraQueryBuilder<Store, Entity, OperatorsMap>();

// Build without any filters or ordering
const { filter, order } = query.build();
// filter: null
// order: null

Chaining Pattern

All methods return this, allowing you to chain operations:

const query = new ChimeraQueryBuilder<Store, User, OperatorsMap>()
  .where("active", "eq", true)
  .where("role", "in", ["admin", "moderator"])
  .group("or", (q) => {
    q.where("age", "gte", 18).where("verified", "eq", true);
  })
  .orderBy("lastActive", true)
  .orderBy("name");

const descriptor = query.build();

Type Safety

The ChimeraQueryBuilder is fully type-safe. It ensures:

  • Property names must exist on the entity type
  • Operator names must exist in the OperatorsMap
  • Test values match the expected types for each operator
  • Property getters are correctly typed
type User = {
  id: string;
  name: string;
  age: number;
  email: string;
};

type OperatorsMap = {
  eq: (value: any) => boolean;
  gte: (value: number) => boolean;
  in: (value: any[]) => boolean;
};

const query = new ChimeraQueryBuilder<Store, User, OperatorsMap>();

// ✅ Valid
query.where("name", "eq", "John");
query.where("age", "gte", 18);
query.where("id", "in", ["1", "2", "3"]);

// ❌ TypeScript errors
query.where("invalidProperty", "eq", "value"); // Property doesn't exist
query.where("age", "invalidOp", 18); // Operator doesn't exist
query.where("age", "eq", "not a number"); // Wrong type for operator

Integration

With Repository

const query = new ChimeraQueryBuilder<typeof store, User, OperatorsMap>()
  .where("active", "eq", true)
  .orderBy("createdAt", true);

// Use with entity store
const users = userStore.getCollection(query.build());

Notes

  • Multiple where() calls are combined with AND logic by default
  • Use group('or', ...) to create OR conditions
  • Use group('not', ...) or whereNot() for negations
  • Order rules are applied in the sequence they're added (first = primary sort)
  • An empty query builder will return { filter: null, order: null }