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

@shkumbinhsn/elasticorm

v0.2.1

Published

A type-safe Elasticsearch ORM for TypeScript, inspired by Drizzle ORM

Readme

ElasticORM

A type-safe Elasticsearch ORM for TypeScript, inspired by Drizzle ORM.

import { ElasticORM, esIndex, esText, esKeyword, esInteger, eq, match, bool, must, filter, range } from '@shkumbinhsn/elasticorm';

// Define your schema
const users = esIndex('users', {
  id: esKeyword().notNull(),
  name: esText({ analyzer: 'standard' }),
  email: esKeyword().notNull(),
  age: esInteger(),
});

// Types are inferred automatically
type User = typeof users.$infer;

// Query with full type safety
const results = await db
  .search(users)
  .where(bool(
    must(match(users.name, 'john')),
    filter(range(users.age, { gte: 18 }))
  ))
  .execute();

Features

  • Schema-first design - Define your Elasticsearch mappings with TypeScript
  • Full type inference - $infer and $insert types generated from schema
  • Type-safe queries - Catch errors at compile time, not runtime
  • Drizzle-like API - Familiar functional patterns
  • Zero dependencies - Uses native fetch, no bloat
  • ES 8.x support - Built for modern Elasticsearch

Installation

bun add @shkumbinhsn/elasticorm
# or
npm install @shkumbinhsn/elasticorm

Quick Start

1. Define Your Schema

import {
  esIndex,
  esText,
  esKeyword,
  esInteger,
  esDate,
  esBoolean,
  esNested
} from '@shkumbinhsn/elasticorm';

export const users = esIndex('users', {
  id: esKeyword().notNull(),
  name: esText({ analyzer: 'standard' }),
  email: esKeyword().notNull(),
  age: esInteger(),
  isActive: esBoolean().default(true),
  createdAt: esDate().default('now'),
  tags: esKeyword().array(),
  address: esNested({
    street: esText(),
    city: esKeyword(),
    country: esKeyword(),
  }),
});

// Infer types from schema
type User = typeof users.$infer;
type InsertUser = typeof users.$insert;

2. Connect to Elasticsearch

import { ElasticORM } from '@shkumbinhsn/elasticorm';

const orm = new ElasticORM({
  node: 'http://localhost:9200',
  // Optional auth:
  // auth: { username: 'elastic', password: 'password' }
  // Or API key:
  // auth: { apiKey: 'your-api-key' }
});

const db = orm.connect({ users });

// Sync schema to Elasticsearch
await db.sync();

3. CRUD Operations

// Create
await db.index(users).values({
  id: '1',
  name: 'John Doe',
  email: '[email protected]',
  age: 30,
}).execute({ id: '1' });

// Read
const user = await db.get(users, '1');

// Update
await db.update(users, '1').set({
  age: 31,
}).execute();

// Delete
await db.delete(users, '1');

// Bulk operations
await db.bulk(users).index([
  { id: '2', name: 'Jane', email: '[email protected]' },
  { id: '3', name: 'Bob', email: '[email protected]' },
]).execute();

4. Type-Safe Queries

import { eq, match, range, bool, must, filter, should } from '@shkumbinhsn/elasticorm';

// Simple term query
const results = await db
  .search(users)
  .where(eq(users.email, '[email protected]'))
  .execute();

// Access results (follows Elasticsearch response structure)
console.log(results.hits.total.value);      // Total count
console.log(results.hits.hits);             // Array of hits
console.log(results.hits.hits[0]._source);  // First document
console.log(results.hits.hits[0]._id);      // Document ID

// Full-text search
const results2 = await db
  .search(users)
  .where(match(users.name, 'john doe'))
  .execute();

// Complex bool query
const results = await db
  .search(users)
  .where(
    bool(
      must(match(users.name, 'john')),
      filter(range(users.age, { gte: 18, lte: 65 })),
      should(eq(users.isActive, true))
    )
  )
  .size(10)
  .from(0)
  .sort(users.createdAt, 'desc')
  .execute();

// Count
const count = await db
  .search(users)
  .where(eq(users.isActive, true))
  .count();

5. Aggregations

import { terms, avg, sum, dateHistogram } from '@shkumbinhsn/elasticorm';

const results = await db
  .search(users)
  .aggregate({
    avgAge: avg(users.age),
    byCountry: terms(users.address.country, { size: 10 }),
    signupsByMonth: dateHistogram(users.createdAt, {
      calendar_interval: 'month'
    }),
  })
  .size(0)
  .execute();

// Results are fully typed
console.log(results.aggregations.avgAge.value);
console.log(results.aggregations.byCountry.buckets);

API Reference

Schema Fields

| Function | Elasticsearch Type | TypeScript Type | |----------|-------------------|-----------------| | esText() | text | string | | esKeyword() | keyword | string | | esInteger() | integer | number | | esLong() | long | number | | esFloat() | float | number | | esDouble() | double | number | | esDate() | date | Date \| string \| number | | esBoolean() | boolean | boolean | | esNested() | nested | object | | esObject() | object | object | | esGeoPoint() | geo_point | GeoPoint | | esIp() | ip | string | | esBinary() | binary | string |

Field Modifiers

esKeyword()           // Optional field
esKeyword().notNull() // Required field
esKeyword().array()   // Array field
esBoolean().default(true)  // Default value

Query Functions

Term-level queries:

  • eq(field, value) - Exact match
  • inValues(field, values) - Match any of values
  • exists(field) - Field exists
  • prefix(field, value) - Prefix match
  • wildcard(field, pattern) - Wildcard match
  • regexp(field, pattern) - Regex match
  • fuzzy(field, value) - Fuzzy match

Range queries:

  • range(field, { gte, lte, gt, lt }) - Range query
  • gt(field, value) - Greater than
  • gte(field, value) - Greater than or equal
  • lt(field, value) - Less than
  • lte(field, value) - Less than or equal

Full-text queries:

  • match(field, query) - Full-text match
  • matchPhrase(field, query) - Phrase match
  • multiMatch(fields, query) - Multi-field match
  • queryString(query) - Query string syntax

Compound queries:

  • bool(clauses...) - Boolean query
  • must(conditions...) - Must match (AND)
  • filter(conditions...) - Filter (no scoring)
  • should(conditions...) - Should match (OR)
  • mustNot(conditions...) - Must not match

Other:

  • matchAll() - Match all documents
  • nested(field, query) - Nested query
  • geoDistance(field, point, distance) - Geo distance

Sorting

// Sort by field
.sort(users.createdAt, 'desc')

// Sort by score
.sortByScore('desc')

// Sort by geo distance (for location-based apps)
.sortGeoDistance(users.location, { lat: 40.7128, lon: -74.0060 }, {
  order: 'asc',
  unit: 'km'
})

// Raw sort for complex configurations
.sortRaw({
  _geo_distance: {
    location: { lat: 40.7, lon: -74 },
    order: 'asc',
    unit: 'km'
  }
})

Aggregations

Bucket aggregations:

  • terms(field) - Terms aggregation
  • histogram(field, { interval }) - Histogram
  • dateHistogram(field, { calendar_interval }) - Date histogram
  • range(field, ranges) - Range buckets
  • filterAgg(condition) - Filter aggregation

Metric aggregations:

  • avg(field) - Average
  • sum(field) - Sum
  • min(field) - Minimum
  • max(field) - Maximum
  • stats(field) - Statistics
  • cardinality(field) - Unique count
  • percentiles(field) - Percentiles
  • topHits() - Top documents

Index Management

// Create index with mappings
await db.createIndex(users);

// Update mappings
await db.updateMapping(users);

// Check if index exists
const exists = await db.indexExists(users);

// Delete index
await db.deleteIndex(users);

// Sync all schemas (create if not exists, update mappings)
await db.sync();

Development

# Install dependencies
bun install

# Run tests
bun test

# Run only unit tests
bun run test:unit

# Run integration tests (requires Elasticsearch)
bun run docker:up
bun run test:integration
bun run docker:down

# Type check
bun run typecheck

License

MIT