@deven96/ahnlich-client-node
v0.2.1
Published
Node.js gRPC client for Ahnlich AI and DB servers
Readme
Ahnlich Node.js Client SDK
A Node.js/TypeScript client that interacts with both Ahnlich DB and AI over gRPC.
Table of Contents
- Installation
- Package Information
- Initialization
- Requests - DB
- Requests - AI
- Tracing
- Development & Testing
- Deploy to npm
- Type Meanings
- Change Log
Installation
npm install ahnlich-client-nodePackage Information
This 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
Initialization
DB Client
import { createDbClient } from "ahnlich-client-node";
const client = createDbClient("127.0.0.1:1369");AI Client
import { createAiClient } from "ahnlich-client-node";
const client = createAiClient("127.0.0.1:1370");With Authentication
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",
});Requests - DB
Ping
import { Ping } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.ping(new Ping());
console.log(response); // PongInfo Server
import { InfoServer } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.infoServer(new InfoServer());
console.log(response.info?.version);List Connected Clients
import { ListClients } from "ahnlich-client-node/grpc/db/query_pb";
const response = await client.listClients(new ListClients());
console.log(response.clients);List Stores
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.
Get Store
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 diskCreate Store
import { 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.
Set
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" } }),
},
}),
}),
],
}),
);Get Sim N
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);Get Key
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);Get By Predicate
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" } }),
}),
},
}),
},
}),
}),
);Create Predicate Index
import { CreatePredIndex } from "ahnlich-client-node/grpc/db/query_pb";
await client.createPredIndex(
new CreatePredIndex({ store: "my_store", predicates: ["label"] }),
);Drop Predicate Index
import { DropPredIndex } from "ahnlich-client-node/grpc/db/query_pb";
await client.dropPredIndex(
new DropPredIndex({ store: "my_store", predicates: ["label"], errorIfNotExists: true }),
);Create Non Linear Algorithm Index
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() } })],
}),
);Drop Non Linear Algorithm Index
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,
}),
);Delete Key
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] })],
}),
);Delete Predicate
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 */,
}),
);Drop Store
import { DropStore } from "ahnlich-client-node/grpc/db/query_pb";
await client.dropStore(new DropStore({ store: "my_store", errorIfNotExists: true }));Requests - AI
Ping
import { Ping } from "ahnlich-client-node/grpc/ai/query_pb";
const response = await client.ping(new Ping());Info Server
import { InfoServer } from "ahnlich-client-node/grpc/ai/query_pb";
const response = await client.infoServer(new InfoServer());List Stores
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));Get Store
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)Create Store
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,
}),
);Set
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,
}),
);Get Sim N
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);Get By Predicate
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 */,
}),
);Create Predicate Index
import { CreatePredIndex } from "ahnlich-client-node/grpc/ai/query_pb";
await client.createPredIndex(
new CreatePredIndex({ store: "ai_store", predicates: ["brand"] }),
);Drop Predicate Index
import { DropPredIndex } from "ahnlich-client-node/grpc/ai/query_pb";
await client.dropPredIndex(
new DropPredIndex({ store: "ai_store", predicates: ["brand"], errorIfNotExists: true }),
);Create Non Linear Algorithm Index
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() } })],
}),
);Drop Non Linear Algorithm Index
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,
}),
);Delete Key
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" } })],
}),
);Drop Store
import { DropStore } from "ahnlich-client-node/grpc/ai/query_pb";
await client.dropStore(new DropStore({ store: "ai_store", errorIfNotExists: true }));Convert Store Input To Embeddings
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!);Tracing
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.
Development & Testing
make install-dependencies
make generate # regenerate TypeScript protobuf code from proto definitions (requires buf)
make format
make lint-check
make testDeploy to npm
Bump 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.
Type Meanings
- 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.)
Change Log
| Version | Description | |---------|--------------------------------------------------| | 0.1.0 | Initial Node.js/TypeScript SDK release via gRPC |
