@qrvey/enginex
v0.0.1-1247-beta.1
Published
Connect to a data engine from an already-resolved config, and read from it through one engine-agnostic contract. Owns the `Enginex.create()` facade, the adapter registry/factory, the `Connector` contract, and errors. Reading env / resolving config lives i
Maintainers
Keywords
Readme
@qrvey/enginex
Connect to a data engine from an already-resolved config, and read from it through one
engine-agnostic contract. Owns the Enginex.create() facade, the adapter registry/factory,
the Connector contract, and errors. Reading env / resolving config lives in
@qrvey/enginex-resolvers — this package never reads env.
Usage
import { Enginex } from '@qrvey/enginex';
import { fromDefaultStorage } from '@qrvey/enginex-resolvers';
const db = await Enginex.create(fromDefaultStorage());
await db.connect(); // autoConnect defaults to false
if (await db.storageExists('dataset_123')) {
const total = await db.count('dataset_123');
const { rows } = await db.query({
statement: 'SELECT * FROM dataset_123 LIMIT 100',
});
}
await db.close();Stream a large read, or read column types — both part of the always-present read core:
for await (const { rows } of db.stream({
statement: 'SELECT * FROM dataset_123',
})) {
handle(rows);
}
const columns = await db.getColumnMetadata('dataset_123');Repository operations are optional — present only when the engine backs them. Gate with
if (db.method), and TypeScript narrows the method to defined inside the block:
if (db.reindex) {
await db.reindex('dataset_123', 'dataset_123_v2'); // ES/OS; ClickHouse omits it
}Connect automatically, or pass a raw definition instead of a resolver:
const db = await Enginex.create(fromDefaultStorage({ autoConnect: true }));
await Enginex.create({
source: 'internal',
engine: 'clickhouse',
config: { url: 'http://clickhouse:8123', database: 'default' },
});
// Elasticsearch / OpenSearch — basic auth, or AWS SigV4 when no user/pass
await Enginex.create({
source: 'internal',
engine: 'opensearch',
config: { url: 'https://opensearch:9200', region: 'us-east-1' },
});Contracts: source & repository
A connector implements one of two contracts, split by role:
SourceConnector— the read surface a data source needs (MySQL, MSSQL, Redshift, Athena…). Read-only by construction; the type has no write methods.RepositoryConnector— extendsSourceConnectorwith the write + management surface, for engines used as a writable data repository (ClickHouse, Elasticsearch, OpenSearch).
Read core (SourceConnector)
| Member | Does |
| ---------------------------------- | ----------------------------------------------- |
| connect() · close() · ping() | open · release · liveness (boolean) |
| query(input) | run an engine-native query → QueryResult |
| execute(input) | raw statement → { rows, raw } (escape hatch) |
| count(storage) | row count → number |
| storageExists(storage) | storage (table/index) exists → boolean |
| getColumnMetadata(storage) | field types → ColumnMetadata[] |
| stream(input) | incremental read → AsyncIterable<QueryResult> |
query(input) runs a ready-to-run, engine-native query — a SQL statement for
ClickHouse, a search DSL body for Elasticsearch/OpenSearch — and returns a normalized
QueryResult = { rows, total?, aggregations?, raw }. The input is executed as-is;
build the native query upstream (e.g. with @qrvey/query-builder). execute is the raw
escape hatch.
Repository surface (RepositoryConnector)
Adds records (bulk, putRecord, …), storage admin (createStorage, reindex, …),
cluster ops, scroll, snapshots, scripts, and native-client access. These optional methods
are present only when the engine backs them — an adapter defines what it supports and
omits the rest. Gate each call with if (db.method); because the optional members are
typed optional, TypeScript enforces the guard and narrows inside the block. There is no
capabilities object and no supports() — presence is the capability.
Errors
Failures throw ConnectError with a stable code (CONFIG_ERROR / CONNECTION_ERROR /
EXECUTION_ERROR / AUTH_ERROR), the failing engine, and the original error on .cause.
Unsupported operations are not an error case — an engine simply omits the methods it does
not back, so callers gate with if (db.method) instead of catching.
