@shkumbinhsn/elasticorm
v0.2.1
Published
A type-safe Elasticsearch ORM for TypeScript, inspired by Drizzle ORM
Maintainers
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 -
$inferand$inserttypes 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/elasticormQuick 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 valueQuery Functions
Term-level queries:
eq(field, value)- Exact matchinValues(field, values)- Match any of valuesexists(field)- Field existsprefix(field, value)- Prefix matchwildcard(field, pattern)- Wildcard matchregexp(field, pattern)- Regex matchfuzzy(field, value)- Fuzzy match
Range queries:
range(field, { gte, lte, gt, lt })- Range querygt(field, value)- Greater thangte(field, value)- Greater than or equallt(field, value)- Less thanlte(field, value)- Less than or equal
Full-text queries:
match(field, query)- Full-text matchmatchPhrase(field, query)- Phrase matchmultiMatch(fields, query)- Multi-field matchqueryString(query)- Query string syntax
Compound queries:
bool(clauses...)- Boolean querymust(conditions...)- Must match (AND)filter(conditions...)- Filter (no scoring)should(conditions...)- Should match (OR)mustNot(conditions...)- Must not match
Other:
matchAll()- Match all documentsnested(field, query)- Nested querygeoDistance(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 aggregationhistogram(field, { interval })- HistogramdateHistogram(field, { calendar_interval })- Date histogramrange(field, ranges)- Range bucketsfilterAgg(condition)- Filter aggregation
Metric aggregations:
avg(field)- Averagesum(field)- Summin(field)- Minimummax(field)- Maximumstats(field)- Statisticscardinality(field)- Unique countpercentiles(field)- PercentilestopHits()- 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 typecheckLicense
MIT
