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

dyno-cql

v1.5.0

Published

A TypeScript library for building CQL queries

Readme

Dyno CQL

Build type-safe OGC Common Query Language (CQL) filter expressions with a fluent TypeScript API.

npm version License: MIT

Why Dyno CQL?

Building complex CQL queries by concatenating strings is a headache. Dyno CQL provides a better way to generate CQL filters:

  • Type safety: Catch typos and invalid types at compile time.
  • Fluent builder: Chain conditions together without worrying about brackets or operator precedence.
  • Automatic escaping: Values are safely formatted and escaped.
  • Standards compliant: Outputs valid OGC CQL2 ready for your geospatial APIs.

Installation

npm install dyno-cql

Quick Start

import { queryBuilder, eq, gt, and } from 'dyno-cql';

// Simple filter
const simple = queryBuilder()
  .filter(eq("status", "ACTIVE"))
  .toCQL();
// → status = 'ACTIVE'

// Complex filter
const complex = queryBuilder()
  .filter(
    and(
      eq("status", "ACTIVE"),
      gt("age", 18)
    )
  )
  .toCQL();
// → (status = 'ACTIVE' AND age > 18)

Type Safety

Define interfaces for your data to get full type safety on attribute names and value types

interface Product {
  id: string;
  name: string;
  price: number;
  inStock: boolean;
}

// Type-safe query builder
const productQuery = queryBuilder<Product>()
  .filter((op) => 
    op.and(
      // the attribute name and value types are checked
      op.eq("name", "Laptop"),        // ✓ Valid attribute
      op.gte("price", 500),           // ✓ Valid type (number)
      op.eq("inStock", true),         // ✓ Valid type (boolean)
      op.isIn("id", ["p1", "p2"]),      // ✓ Valid list values
      // op.eq("invalid", "val")      // ✗ Compile error
    )
  )

API Reference

Comparison Operators

Text Operators

Logical Operators

Spatial Operators

Temporal Operators

Simple CQL:

Enhanced operators:

Comparison Operators

Standard value comparisons for filtering your data.

import { eq, ne, lt, lte, gt, gte, between, isIn, isNotIn, isNull, isNotNull } from 'dyno-cql';

// Equality
eq("status", "ACTIVE")        // → status = 'ACTIVE'
ne("status", "DELETED")       // → status <> 'DELETED'

// Numeric comparisons
lt("age", 18)                 // → age < 18
lte("score", 100)             // → score <= 100
gt("price", 50)               // → price > 50
gte("quantity", 5)            // → quantity >= 5

// Range queries
between("age", 18, 65)        // → age BETWEEN 18 AND 65

// List membership
isIn("investigation_type", ["CPT", "OTHER"]) // → investigation_type IN ('CPT', 'OTHER')
isNotIn("status", ["DELETED", "ARCHIVED"])     // → status NOT IN ('DELETED', 'ARCHIVED')

// Null checks
isNull("deletedAt")           // → deletedAt IS NULL
isNotNull("email")            // → email IS NOT NULL

Text Operators

String matching for search functionality.

import { like, contains } from 'dyno-cql';

// Wildcard matching
like("name", "A%")                   // → name LIKE 'A%'

// Substring search
contains("description", "important")  // → description LIKE '%important%'

Logical Operators

Combine multiple conditions to build complex filters.

import { and, or, not, eq, gt } from 'dyno-cql';

// AND - all conditions must match
and(
  eq("status", "ACTIVE"),
  gt("age", 18)
)
// → (status = 'ACTIVE' AND age > 18)

// OR - any condition can match
or(
  eq("status", "PENDING"),
  eq("status", "PROCESSING")
)
// → (status = 'PENDING' OR status = 'PROCESSING')

// NOT - negate a condition
not(eq("status", "DELETED"))
// → NOT (status = 'DELETED')

Spatial Operators

Filter geospatial data using GeoJSON geometries. Input uses GeoJSON, output converts to WKT (Well-Known Text) format.

import {
  intersects,
  disjoint,
  spatialContains,
  within,
  touches,
  overlaps,
  crosses,
  spatialEquals
} from 'dyno-cql';

// Point geometry
const point = { type: "Point", coordinates: [0, 0] };
intersects("geometry", point)
// → INTERSECTS(geometry, POINT (0 0))

// Polygon geometry
const polygon = {
  type: "Polygon",
  coordinates: [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]]
};
within("geometry", polygon)
// → WITHIN(geometry, POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)))

// Line geometry
const line = {
  type: "LineString",
  coordinates: [[0, 0], [1, 1]]
};
crosses("geometry", line)
// → CROSSES(geometry, LINESTRING (0 0, 1 1))

Available spatial operators

| Operator | Description | |----------|-------------| | intersects | Geometries share any space | | disjoint | Geometries share no space | | spatialContains | First geometry contains the second | | within | First geometry is within the second | | touches | Geometries touch at boundary only | | overlaps | Geometries overlap but neither contains the other | | crosses | Geometries cross each other | | spatialEquals | Geometries are spatially equal |

Temporal Operators

Filter data by time and date relationships. Supports ISO 8601 timestamps, Date objects, and intervals.

Simple temporal filtering

The anyinteracts operator is the broadest temporal filter. It evaluates to true if the property has any interaction or overlap (e.g., contains, intersects, touches, or equals) with the provided time. It supports ISO 8601 strings, Date objects, and time intervals.

import { anyinteracts } from 'dyno-cql';

// Match a specific timestamp (string or Date)
anyinteracts("eventDate", "2023-01-01T00:00:00Z")
// → ANYINTERACTS(eventDate, TIMESTAMP('2023-01-01T00:00:00Z'))

// Match a time interval (object with start and end)
anyinteracts("eventDate", { start: "2023-01-01", end: "2023-12-31" })
// → ANYINTERACTS(eventDate, INTERVAL('2023-01-01', '2023-12-31'))

Point-in-time operators

import { after, before, tequals } from 'dyno-cql';

// After a timestamp
after("eventDate", "2023-01-01T00:00:00Z")
// → AFTER(eventDate, TIMESTAMP('2023-01-01T00:00:00Z'))

// Before a timestamp
before("eventDate", "2023-12-31T23:59:59Z")
// → BEFORE(eventDate, TIMESTAMP('2023-12-31T23:59:59Z'))

// Equal to a timestamp
tequals("eventDate", "2023-06-15T12:00:00Z")
// → TEQUALS(eventDate, TIMESTAMP('2023-06-15T12:00:00Z'))

// Works with Date objects too
const date = new Date("2023-06-15");
after("eventDate", date)
// → AFTER(eventDate, TIMESTAMP('2023-06-15T00:00:00.000Z'))

Interval operators

import { during, toverlaps, overlappedby, tcontains } from 'dyno-cql';

// Event occurs during an interval
during("eventDate", { start: "2023-01-01", end: "2023-12-31" })
// → DURING(eventDate, INTERVAL('2023-01-01', '2023-12-31'))

// Event period overlaps an interval
toverlaps("eventPeriod", { start: "2023-06-01", end: "2023-12-31" })
// → TOVERLAPS(eventPeriod, INTERVAL('2023-06-01', '2023-12-31'))

// Event period is overlapped by an interval
overlappedby("eventPeriod", { start: "2022-06-01", end: "2023-06-30" })
// → OVERLAPPEDBY(eventPeriod, INTERVAL('2022-06-01', '2023-06-30'))

// Event period contains a timestamp
tcontains("eventPeriod", "2023-06-15T12:00:00Z")
// → TCONTAINS(eventPeriod, TIMESTAMP('2023-06-15T12:00:00Z'))

Boundary operators

import { begins, begunby, ends, endedby, meets, metby, tintersects } from 'dyno-cql';

// Period begins at timestamp
begins("eventPeriod", "2023-01-01T00:00:00Z")
// → BEGINS(eventPeriod, TIMESTAMP('2023-01-01T00:00:00Z'))

// Period is begun by timestamp
begunby("eventPeriod", "2023-01-01T00:00:00Z")
// → BEGUNBY(eventPeriod, TIMESTAMP('2023-01-01T00:00:00Z'))

// Period ends at timestamp
ends("eventPeriod", "2023-12-31T23:59:59Z")
// → ENDS(eventPeriod, TIMESTAMP('2023-12-31T23:59:59Z'))

// Period is ended by timestamp
endedby("eventPeriod", "2023-12-31T23:59:59Z")
// → ENDEDBY(eventPeriod, TIMESTAMP('2023-12-31T23:59:59Z'))

// Periods meet (one ends when other begins)
meets("eventPeriod", { start: "2023-07-01", end: "2023-12-31" })
// → MEETS(eventPeriod, INTERVAL('2023-07-01', '2023-12-31'))

// Periods are met by (other ends when one begins)
metby("eventPeriod", { start: "2022-07-01", end: "2023-01-01" })
// → METBY(eventPeriod, INTERVAL('2022-07-01', '2023-01-01'))

// Temporal intersection
tintersects("eventDate", "2023-06-15T12:00:00Z")
// → TINTERSECTS(eventDate, TIMESTAMP('2023-06-15T12:00:00Z'))

Advanced Usage

Reusable conditions

Build conditions once, use them everywhere.

import { queryBuilder, eq, and, gt, ne } from 'dyno-cql';

// Define reusable conditions
const isActive = eq("status", "ACTIVE");
const isAdult = gt("age", 18);
const notDeleted = ne("deleted", true);

// Combine them
const standardFilters = and(isActive, notDeleted);

// Use in multiple queries
const query1 = queryBuilder()
  .filter(and(standardFilters, eq("type", "premium")))
  .toCQL();

const query2 = queryBuilder()
  .filter(and(standardFilters, isAdult))
  .toCQL();

Clone and modify queries

Start with a base query and create variations.

import { queryBuilder, eq, and } from 'dyno-cql';

// Base query
const baseQuery = queryBuilder()
  .filter(eq("type", "product"));

// Create variations
const activeProducts = baseQuery.clone()
  .filter(and(eq("type", "product"), eq("status", "ACTIVE")));

const archivedProducts = baseQuery.clone()
  .filter(and(eq("type", "product"), eq("archived", true)));

URL-safe output

Generate encoded strings ready for URL parameters.

import { queryBuilder, and, eq, contains } from 'dyno-cql';

const query = queryBuilder()
  .filter(
    and(
      eq("name", "John & Jane"),
      contains("description", "100% satisfaction")
    )
  )
  .toCQLUrlSafe();

// Use directly in fetch
fetch(`/api/products?filter=${query}`);

License

MIT