A Node.js/TypeScript client that interacts with both Ahnlich DB and AI over gRPC.
- Installation
- Package Information
- Initialization
- Requests - DB
- Requests - AI
- Tracing
- Development & Testing
- Deploy to npm
- Type Meanings
- Change Log
npm install ahnlich-client-nodeThis package provides:
- gRPC service clients for DB and AI via
@connectrpc/connect - TypeScript types generated from the Ahnlich
.protodefinitions - Optional auth (TLS + bearer token) and trace ID support
import { createDbClient } from "ahnlich-client-node";
const client = createDbClient("127.0.0.1:1369");import { createAiClient } from "ahnlich-client-node";
const client = createAiClient("127.0.0.1:1370");When the server is started with --enable-auth, pass a CA certificate and credentials:
import * as fs from "fs";
import { createDbClient } from "ahnlich-client-node";
const client = createDbClient("127.0.0.1:1369", {
caCert: fs.readFileSync("ca.crt"),
auth: { username: "myuser", apiKey: "mykey" },
});Pass a trace ID to correlate requests across services:
const client = createDbClient("127.0.0.1:1369", {
traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
});import { Ping } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.ping(new Ping());
console.log(response); // Pongimport { InfoServer } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.infoServer(new InfoServer());
console.log(response.info?.version);import { ListClients } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.listClients(new ListClients());
console.log(response.clients);import { ListStores } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.listStores(new ListStores());
console.log(response.stores.map((s) => s.name));Each StoreInfo object in response.stores includes name, len, sizeInBytes, nonLinearIndices, predicateIndices, and dimension.
Returns detailed information about a single store by name.
import { GetStore } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.getStore(new GetStore({ store: "my_store" }));
console.log(response.name); // store name
console.log(response.dimension); // vector dimension
console.log(response.predicateIndices); // indexed predicate keys
console.log(response.nonLinearIndices); // non-linear algorithm indices
console.log(response.len); // number of entries
console.log(response.sizeInBytes); // size on diskimport { CreateStore } from "ahnlich-client-node/grpc/db/query_pb";
await client.createStore(
new CreateStore({
store: "my_store",
dimension: 4,
predicates: ["label"],
errorIfExists: true,
}),
);Store dimension is fixed at creation — all inserted vectors must match it.
import { Set } from "ahnlich-client-node/grpc/db/query_pb";
import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";
await client.set(
new Set({
store: "my_store",
inputs: [
new DbStoreEntry({
key: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }),
value: new StoreValue({
value: {
label: new MetadataValue({ value: { case: "rawString", value: "A" } }),
},
}),
}),
],
}),
);Returns the closest N entries to a query vector.
import { GetSimN } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";
import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb";
const response = await client.getSimN(
new GetSimN({
store: "my_store",
searchInput: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }),
closestN: 3,
algorithm: Algorithm.COSINE_SIMILARITY,
}),
);
console.log(response.entries);import { GetKey } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";
const response = await client.getKey(
new GetKey({
store: "my_store",
keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })],
}),
);
console.log(response.entries);import { GetPred } from "ahnlich-client-node/grpc/db/query_pb";
import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";
const response = await client.getPred(
new GetPred({
store: "my_store",
condition: new PredicateCondition({
kind: {
case: "value",
value: new Predicate({
kind: {
case: "equals",
value: new Equals({
key: "label",
value: new MetadataValue({ value: { case: "rawString", value: "A" } }),
}),
},
}),
},
}),
}),
);import { CreatePredIndex } from "ahnlich-client-node/grpc/db/query_pb";
await client.createPredIndex(
new CreatePredIndex({ store: "my_store", predicates: ["label"] }),
);import { DropPredIndex } from "ahnlich-client-node/grpc/db/query_pb";
await client.dropPredIndex(
new DropPredIndex({ store: "my_store", predicates: ["label"], errorIfNotExists: true }),
);import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb";
import { NonLinearIndex, KDTreeConfig, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";
// Create a KDTree index
await client.createNonLinearAlgorithmIndex(
new CreateNonLinearAlgorithmIndex({
store: "my_store",
nonLinearIndices: [new NonLinearIndex({ index: { case: "kdtree", value: new KDTreeConfig() } })],
}),
);
// Or create an HNSW index (with optional config)
await client.createNonLinearAlgorithmIndex(
new CreateNonLinearAlgorithmIndex({
store: "my_store",
nonLinearIndices: [new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() } })],
}),
);import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb";
import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";
await client.dropNonLinearAlgorithmIndex(
new DropNonLinearAlgorithmIndex({
store: "my_store",
nonLinearIndices: [NonLinearAlgorithm.KDTree],
errorIfNotExists: true,
}),
);import { DelKey } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";
await client.delKey(
new DelKey({
store: "my_store",
keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })],
}),
);import { DelPred } from "ahnlich-client-node/grpc/db/query_pb";
await client.delPred(
new DelPred({
store: "my_store",
condition: /* same PredicateCondition as Get By Predicate */,
}),
);import { DropStore } from "ahnlich-client-node/grpc/db/query_pb";
await client.dropStore(new DropStore({ store: "my_store", errorIfNotExists: true }));import { Ping } from "ahnlich-client-node/grpc/ai/query_pb";
const response = await client.ping(new Ping());import { InfoServer } from "ahnlich-client-node/grpc/ai/query_pb";
const response = await client.infoServer(new InfoServer());import { ListStores } from "ahnlich-client-node/grpc/ai/query_pb";
const response = await client.listStores(new ListStores());
console.log(response.stores.map((s) => s.name));Returns detailed information about a single AI store by name.
import { GetStore } from "ahnlich-client-node/grpc/ai/query_pb";
const response = await client.getStore(new GetStore({ store: "ai_store" }));
console.log(response.name); // store name
console.log(response.queryModel); // AI model used for querying
console.log(response.indexModel); // AI model used for indexing
console.log(response.embeddingSize); // number of stored embeddings
console.log(response.dimension); // vector dimension
console.log(response.predicateIndices); // indexed predicate keys
console.log(response.dbInfo); // optional DB store info (when AI is connected to DB)import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb";
import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb";
await client.createStore(
new CreateStore({
store: "ai_store",
queryModel: AIModel.ALL_MINI_LM_L6_V2,
indexModel: AIModel.ALL_MINI_LM_L6_V2,
predicates: ["brand"],
errorIfExists: true,
storeOriginal: true,
}),
);import { Set } from "ahnlich-client-node/grpc/ai/query_pb";
import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";
import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb";
await client.set(
new Set({
store: "ai_store",
inputs: [
new AiStoreEntry({
key: new StoreInput({ value: { case: "rawString", value: "Jordan One" } }),
value: new StoreValue({
value: {
brand: new MetadataValue({ value: { case: "rawString", value: "Nike" } }),
},
}),
}),
],
preprocessAction: PreprocessAction.NO_PREPROCESSING,
}),
);import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb";
import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb";
import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb";
const response = await client.getSimN(
new GetSimN({
store: "ai_store",
searchInput: new StoreInput({ value: { case: "rawString", value: "Jordan" } }),
closestN: 3,
algorithm: Algorithm.COSINE_SIMILARITY,
}),
);
console.log(response.entries);import { GetPred } from "ahnlich-client-node/grpc/ai/query_pb";
const response = await client.getPred(
new GetPred({
store: "ai_store",
condition: /* PredicateCondition — same structure as DB */,
}),
);import { CreatePredIndex } from "ahnlich-client-node/grpc/ai/query_pb";
await client.createPredIndex(
new CreatePredIndex({ store: "ai_store", predicates: ["brand"] }),
);import { DropPredIndex } from "ahnlich-client-node/grpc/ai/query_pb";
await client.dropPredIndex(
new DropPredIndex({ store: "ai_store", predicates: ["brand"], errorIfNotExists: true }),
);import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb";
import { NonLinearIndex, KDTreeConfig, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";
// Create a KDTree index
await client.createNonLinearAlgorithmIndex(
new CreateNonLinearAlgorithmIndex({
store: "ai_store",
nonLinearIndices: [new NonLinearIndex({ index: { case: "kdtree", value: new KDTreeConfig() } })],
}),
);
// Or create an HNSW index (with optional config)
await client.createNonLinearAlgorithmIndex(
new CreateNonLinearAlgorithmIndex({
store: "ai_store",
nonLinearIndices: [new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() } })],
}),
);import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb";
import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";
await client.dropNonLinearAlgorithmIndex(
new DropNonLinearAlgorithmIndex({
store: "ai_store",
nonLinearIndices: [NonLinearAlgorithm.KDTree],
errorIfNotExists: true,
}),
);import { DelKey } from "ahnlich-client-node/grpc/ai/query_pb";
import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb";
await client.delKey(
new DelKey({
store: "ai_store",
keys: [new StoreInput({ value: { case: "rawString", value: "Jordan One" } })],
}),
);import { DropStore } from "ahnlich-client-node/grpc/ai/query_pb";
await client.dropStore(new DropStore({ store: "ai_store", errorIfNotExists: true }));Converts raw inputs (text, images, audio) into embeddings without storing them.
Basic Example:
import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { AiService } from "./grpc/services/ai_service_connect";
import { AiModel, PreprocessAction } from "./grpc/ai";
const transport = createConnectTransport({
baseUrl: "http://localhost:1370",
httpVersion: "2",
});
const client = createPromiseClient(AiService, transport);
const inputs = [{ rawString: "Hello world" }];
const response = await client.convertStoreInputToEmbeddings({
storeInputs: inputs,
preprocessAction: PreprocessAction.NO_PREPROCESSING,
model: AiModel.ALL_MINI_LM_L6_V2,
});
// Access embeddings
for (const item of response.values) {
if (item.variant.case === "single") {
const embedding = item.variant.value.embedding;
console.log(`Embedding size: ${embedding.key.length}`);
}
}Face Detection with Bounding Box Metadata (v0.2.1+):
Buffalo-L and SFace models return normalized bounding boxes (0-1 range) and confidence scores:
import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { AiService } from "./grpc/services/ai_service_connect";
import { AiModel, PreprocessAction } from "./grpc/ai";
import * as fs from "fs";
const transport = createConnectTransport({
baseUrl: "http://localhost:1370",
httpVersion: "2",
});
const client = createPromiseClient(AiService, transport);
// Load image
const imageBytes = fs.readFileSync("group_photo.jpg");
const inputs = [{ image: imageBytes }];
const response = await client.convertStoreInputToEmbeddings({
storeInputs: inputs,
preprocessAction: PreprocessAction.MODEL_PREPROCESSING,
model: AiModel.BUFFALO_L,
});
// Process detected faces with metadata
for (const item of response.values) {
if (item.variant.case === "multiple") {
const faces = item.variant.value.embeddings;
console.log(`Detected ${faces.length} faces`);
for (const [i, faceData] of faces.entries()) {
// Access embedding
const embedding = faceData.embedding!.key; // Float32Array
console.log(`Face ${i}: ${embedding.length}-dim embedding`);
// Access bounding box metadata
if (faceData.metadata) {
const metadata = faceData.metadata.value;
const bboxX1 = parseFloat(metadata.bbox_x1!.value!.value as string);
const bboxY1 = parseFloat(metadata.bbox_y1!.value!.value as string);
const bboxX2 = parseFloat(metadata.bbox_x2!.value!.value as string);
const bboxY2 = parseFloat(metadata.bbox_y2!.value!.value as string);
const confidence = parseFloat(metadata.confidence!.value!.value as string);
console.log(` BBox: (${bboxX1.toFixed(3)}, ${bboxY1.toFixed(3)}) ` +
`to (${bboxX2.toFixed(3)}, ${bboxY2.toFixed(3)})`);
console.log(` Confidence: ${confidence.toFixed(3)}`);
}
}
}
}Metadata Fields:
bbox_x1,bbox_y1,bbox_x2,bbox_y2: Normalized coordinates (0.0-1.0)confidence: Detection confidence score (0.0-1.0)
To convert to pixel coordinates:
import sharp from "sharp";
const img = sharp("photo.jpg");
const { width, height } = await img.metadata();
const pixelX1 = Math.round(bboxX1 * width!);
const pixelY1 = Math.round(bboxY1 * height!);Pass a W3C trace ID to correlate requests across services:
const client = createDbClient("127.0.0.1:1369", {
traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
});This sets the ahnlich-trace-id header on every request.
make install-dependencies
make generate # regenerate TypeScript protobuf code from proto definitions (requires buf)
make format
make lint-check
make testBump the version field in package.json. When your PR is merged into main, the CI will detect the version change and automatically publish to npm.
- StoreKey: A one-dimensional
float32vector of fixed dimension - StoreValue: A map of string keys to
MetadataValue(text or binary) - StoreInput: A raw string or binary blob accepted by the AI proxy
- Predicates: Filter conditions for stored values (
Equals,NotEquals,In, etc.) - PredicateCondition: Combines one or more predicates with
AND,OR, orValue - AIModel: Supported embedding models (
ALL_MINI_LM_L6_V2,RESNET50,BUFFALO_L, etc.)
| Version | Description |
|---|---|
| 0.1.0 | Initial Node.js/TypeScript SDK release via gRPC |