Node.js client library for MygramDB — a high-performance in-memory full-text search engine with MySQL replication support.
Server compatibility: MygramDB 1.6 or later, with the protocol implemented through 1.10.2. A server rejects options it predates, and an older server's ERROR frames carry no numeric code; the API reference marks each option that needs a newer server.
MygramDB answers full-text queries from memory instead of an on-disk MySQL FULLTEXT index. How much that gains depends on the query and the dataset; the published benchmarks give the numbers together with the conditions they were measured under. This client supports both a pure JavaScript implementation and optional C++ native bindings for maximum performance.
| MySQL FULLTEXT | MygramDB | |
|---|---|---|
| Search Speed | Baseline | Measured |
| Storage | On-disk | In-memory |
| Replication | — | MySQL binlog |
| Protocol | MySQL | TCP (memcached-style) |
- Dual Implementation — Optional C++ native bindings with automatic JavaScript fallback
- Search Expression Parser — Web-style search syntax (+required, -excluded, "phrase", OR, grouping)
- Full Protocol Support — All MygramDB commands (SEARCH, COUNT, GET, INFO, etc.)
- Connection Pool — Built-in
MygramPoolfor hundreds of req/s, with backpressure, load shedding, self-healing reconnects, and an optional circuit breaker - Resilience — Pool circuit breaker (fail fast when the server is unreachable) and standalone-client
autoReconnect - Typed Errors — Numeric server error codes on
ServerError, so retry decisions never depend on message text - IPv4 and IPv6 — Connects to IPv6 literals and to hostnames that resolve only to
AAAArecords, trying every resolved address - Type Safety — Full TypeScript definitions
- Promise-based API — Modern async/await interface
npm install mygramdb-clientOr use yarn/pnpm:
yarn add mygramdb-client
pnpm add mygramdb-clientimport { createMygramClient } from 'mygramdb-client';
const client = createMygramClient({
host: 'localhost',
port: 11016
});
await client.connect();
// Search
const results = await client.search('articles', 'hello');
console.log(`Found ${results.totalCount} results`);
// Count
const count = await client.count('articles', 'technology');
// Get document by ID
const doc = await client.get('articles', '12345');
client.disconnect();A single client serializes every command through one socket. For high
throughput (hundreds of req/s), use the built-in MygramPool, which fans
requests across N connections with backpressure and self-healing reconnects:
import { MygramPool } from 'mygramdb-client';
const pool = new MygramPool({ connection: { host: 'localhost' }, size: 12 });
await pool.start(); // optional warm-up; the first query starts the pool lazily
const results = await pool.search('articles', 'hello', { limit: 100 });
console.log(pool.metrics());
await pool.close();Add circuitBreaker to make the pool fail fast with CircuitOpenError when the
server is unreachable, and onEvent for discrete lifecycle events. A standalone
MygramClient can set autoReconnect to reconnect-and-resend once on a
pre-write dead socket. See
Connection Pooling
for sizing guidance and
Circuit breaker
for the resilience features.
convertSearchExpression() turns web-style input into a server boolean query:
unprefixed terms and + terms are joined with AND, - terms become
AND NOT, and an OR chain stays in parentheses.
search() sends its query as literal text, so a boolean expression goes through
searchRaw(), or through search() with queryMode: 'boolean' when it also
needs filters, sorting, fuzzy matching or highlighting:
import { convertSearchExpression } from 'mygramdb-client';
const raw = convertSearchExpression('golang "machine learning" -php +(tutorial OR guide)');
// → 'golang AND "machine learning" AND (tutorial OR guide) AND NOT php'
const res = await client.searchRaw('articles', raw, { limit: 50 });
await client.search('articles', raw, {
queryMode: 'boolean',
filters: { status: 'published' },
sortColumn: '_score'
});In the default literal mode, plain user text keeps matching as a phrase. For
input without OR or grouping, simplifySearchExpression() splits it into a main
term plus AND/NOT terms for search(); it throws on OR or grouping, so check
with hasComplexExpression() first when the input may contain either:
import {
convertSearchExpression,
hasComplexExpression,
parseSearchExpression,
simplifySearchExpression
} from 'mygramdb-client';
const parsed = parseSearchExpression(userInput);
let results;
if (hasComplexExpression(parsed)) {
results = await client.searchRaw('articles', convertSearchExpression(userInput));
} else {
const { mainTerm, andTerms, notTerms } = simplifySearchExpression(userInput);
results = await client.search('articles', mainTerm, {
andTerms,
notTerms,
limit: 100,
filters: { status: 'published', lang: 'en' },
sortColumn: 'created_at',
sortDesc: true
});
}Sort by relevance using the special _score sort column (requires
verify_text: ascii|all on the server):
const results = await client.search('articles', 'machine learning', {
sortColumn: '_score',
sortDesc: true,
limit: 10
});// Allow up to 1 edit (default) or 2 edits.
const results = await client.search('articles', 'machne', {
fuzzy: 1,
limit: 10
});const results = await client.search('articles', 'golang', {
highlight: {
openTag: '<strong>',
closeTag: '</strong>',
snippetLen: 200,
maxFragments: 3
},
sortColumn: '_score',
sortDesc: true,
limit: 10
});
for (const r of results.results) {
console.log(r.primaryKey, r.snippet);
}Pass an empty {} to enable highlighting with server defaults
(<em>/</em>, 100 code points, up to 3 fragments).
Filters accept =, !=, <>, >, >=, < and <=. Use the array form when
one column needs two conditions:
await client.search('products', 'laptop', {
filters: [
{ column: 'price', op: '>=', value: '100' },
{ column: 'price', op: '<=', value: '500' }
]
});Aggregate distinct filter-column values with document counts, optionally
scoped to a search result set, and page through them with limit/offset:
// Top categories among documents matching "machine learning":
const top = await client.facet('articles', 'category', {
query: 'machine learning',
filters: { status: '1' },
limit: 10
});
for (const v of top.results) {
console.log(`${v.value}: ${v.count}`);
}
const page = await client.facet('articles', 'category', { limit: 20, offset: 40 });
console.log(`${page.results.length} of ${page.totalCount} categories`);A server can index tables from more than one database. Reference a table as
database.table; bare names work on single-database servers.
await client.search('app_db.articles', 'hello');
import { qualifyTableIdentity, parseTableIdentity } from 'mygramdb-client';
qualifyTableIdentity('articles', 'app_db'); // 'app_db.articles'
parseTableIdentity('app_db.articles'); // { database: 'app_db', table: 'articles' }Search terms, filter values, AND/NOT terms, highlight tags, primary keys
and command arguments all go through the same quoting decision. A value is
quoted when it is empty, a reserved clause keyword (AND, OR, NOT,
FILTER, SORT, LIMIT, OFFSET, HIGHLIGHT, FUZZY, FACET, ORDER,
matched case-insensitively), or contains ASCII or Unicode whitespace
(including the full-width and no-break space a pasted value or a full-width
IME can carry), a control character, a quote, a backslash or a parenthesis.
Callers always pass the raw, unquoted text:
// The full-width space stays inside one term.
await client.search('articles', '機械学習 チュートリアル');
// A filter value equal to a reserved keyword still matches literally.
await client.search('articles', 'q', { filters: { status: 'AND' } });get() quotes a primary key that contains whitespace or equals a reserved word,
so a key returned by search() can always be passed back to get() unchanged.
Search results and get() documents decode a primary key or string value the
server quoted the same way.
The server gates administrative commands (DUMP *, REPLICATION *,
SYNC *, CONFIG *, OPTIMIZE, DEBUG *, CACHE *, SET,
SHOW VARIABLES) behind AUTH. Set adminToken and the client authenticates
on every connect, reconnects and pooled connections included:
const client = new MygramClient({ adminToken: process.env.MYGRAM_ADMIN_TOKEN });
await client.connect();
await client.dumpSave('/var/lib/mygramdb/dump.mgd');Ordinary search traffic needs no token.
ERROR frames carry a numeric code, so failures can be classified without
matching message text. Server rejections arrive as ServerError, a subclass of
ProtocolError:
import { ErrorCode, ServerError, isRetryableErrorCode } from 'mygramdb-client';
try {
await client.search('articles', 'hello');
} catch (error) {
if (error instanceof ServerError && isRetryableErrorCode(error.code)) {
// 6028 loading / 6029 not ready / 6030 busy — back off and retry
}
}const info = await client.info();
if (info.ready === false) {
// the server is up but not yet serving queries
}getReplicationStatus() reports secondsSinceLastApplied, stamped where the
replication position advances, so it measures progress rather than
connectivity. It is an administrative command, so a server with a token
configured needs adminToken to answer it — unlike the readiness fields on
INFO. Dumps and OPTIMIZE get their own deadlines, leaving timeout short
enough to detect a stalled query:
const client = new MygramClient({ timeout: 3000, dumpSaveTimeout: 900_000 });
const status = await client.getReplicationStatus();
if ((status.secondsSinceLastApplied ?? 0) > 60) {
console.warn(`replication is ${status.secondsSinceLastApplied}s behind`, status.lastError);
}await client.setVariable('logging.level', 'info');
console.log(await client.showVariables('logging%'));
await client.sync('app_db.articles');
console.log(await client.syncStatus());
await client.syncStop('app_db.articles');Full type definitions are included:
import type {
ClientConfig,
SearchResponse,
CountResponse,
Document,
ServerInfo,
SearchOptions
} from 'mygramdb-client';- Getting Started — install, configuration, and error handling
- Search Expressions — parse and convert web-style search input
- API Reference — every method, option, and type
- Advanced Usage — connection pooling, resilience, authentication, and error codes
yarn install # Install dependencies
yarn build # Build library
yarn test # Run tests
yarn lint # Lint and format check
yarn lint:fix # Auto-fix lint + format issues