Node.js Client Library

The official Node.js client for Geode provides a high-performance QUIC + TLS 1.3 implementation with full TypeScript types. Use createClient for a ready-to-use client with optional pooling.

Installation

npm install @geodedb/client

Requirements: Node.js 20+

Quick Start

import { createClient } from '@geodedb/client';

const client = await createClient('quic://localhost:3141');

const rows = await client.queryAll(
  'MATCH (p:Person) RETURN p.name AS name, p.age AS age'
);

for (const row of rows) {
  console.log(`${row.name}: ${row.age}`);
}

await client.close();

DSN Options

quic://[username:password@]host[:port][?options]

Supported options: page_size, hello_name, hello_ver, conformance, username/user, password/pass, ca, cert, key, insecure_tls_skip_verify, server_name, connect_timeout, request_timeout.

Environment Defaults

  • GEODE_HOST
  • GEODE_PORT
  • GEODE_TLS_CA
  • GEODE_USERNAME
  • GEODE_PASSWORD

Pooling

Pooling is enabled by default. Configure it via ClientOptions:

const client = await createClient('quic://localhost:3141', {
  pooling: true,
  pool: {
    min: 2,
    max: 10,
    acquireTimeout: 30000,
    idleTimeout: 60000,
  },
});

Disable pooling for a single dedicated connection:

const client = await createClient('quic://localhost:3141', { pooling: false });

Queries

const rows = await client.queryAll('MATCH (n) RETURN n.name AS name');
const first = await client.queryFirst('MATCH (n) RETURN n LIMIT 1');
const count = await client.queryScalar<number>('MATCH (n) RETURN count(n) AS cnt', 'cnt');

Streaming Results

const result = await client.query('MATCH (p:Person) RETURN p.name AS name');
for await (const row of result) {
  console.log(row.get('name')?.asString);
}

Transactions

await client.withTransaction(async (tx) => {
  await tx.exec("CREATE (:Person {name: 'Alice'})");
  await tx.exec("CREATE (:Person {name: 'Bob'})");
});

Manual control with savepoints:

const conn = await client.getConnection();
const tx = await conn.begin();
try {
  await tx.exec("CREATE (p:Person {name: 'Alice'})");
  await tx.savepoint('before_update');
  await tx.exec("MATCH (p:Person {name: 'Alice'}) SET p.age = 30");
  await tx.rollbackTo('before_update');
  await tx.commit();
} finally {
  await client.releaseConnection(conn);
}

Prepared Statements

const stmt = await client.prepare('MATCH (p:Person {name: $name}) RETURN p');
const rows = await stmt.executeAll({ name: 'Alice' });

Query Builder

import { query } from '@geodedb/client';

const { query: gql, params } = query()
  .match('(p:Person)')
  .return('p.name AS name')
  .build();

const rows = await client.queryAll(gql, { params });

Error Handling

import {
  DriverError,
  TransportError,
  ConfigError,
  SecurityError,
  isRetryableError,
} from '@geodedb/client';

try {
  await client.queryAll('INVALID QUERY');
} catch (e) {
  if (e instanceof DriverError) {
    console.log('Server error:', e.code, e.message);
    console.log('Status class:', e.statusClass); // ISO 39075 code

    if (isRetryableError(e)) {
      // Serialization or deadlock - can retry
    }
  } else if (e instanceof TransportError) {
    console.log('Network error:', e.operation, e.message);
  } else if (e instanceof ConfigError) {
    console.log('Configuration error:', e.field, e.message);
  } else if (e instanceof SecurityError) {
    console.log('Security error:', e.type, e.message);
  }
}

ISO 39075 Status Classes

ClassMeaningRetryable
00000Success-
01000WarningNo
02000No dataNo
25000Invalid transaction stateNo
28000Authorization errorNo
40001Serialization failureYes
40502Transaction deadlockYes
42000Syntax errorNo
23000Constraint violationNo
58000System errorNo

Type System

The client provides a comprehensive GQL type system:

import { GQLValue, fromJSON } from '@geodedb/client';

// Create typed values
const intVal = GQLValue.int(42);
const strVal = GQLValue.string('hello');
const arrVal = GQLValue.array([GQLValue.int(1), GQLValue.int(2)]);
const nodeVal = GQLValue.node({
  id: '123',
  labels: ['Person'],
  properties: { name: 'Alice' },
});

// Type-safe access
intVal.asNumber; // 42
intVal.asInt; // 42n (bigint)
strVal.asString; // 'hello'
arrVal.asArray; // [GQLValue, GQLValue]
nodeVal.asNode; // { id, labels, properties }

// Convert to plain JS
intVal.toJS(); // 42
nodeVal.toJS(); // { id: '123', labels: ['Person'], ... }

// Parse from JSON
const value = fromJSON({ name: 'Alice', age: 30 });

Supported Types

GQL TypeNode.js Type
INTnumber/bigint
FLOATnumber
DECIMALDecimal
STRINGstring
BOOLboolean
NULLnull
ARRAY/LISTArray
OBJECT/MAPObject/Map
NODEGQLNode
EDGEGQLEdge
PATHGQLPath
BYTEAUint8Array
DATEDate
TIMEDate
TIMESTAMPDate
UUIDstring
JSON/JSONBany

Advanced Features

Batch Operations

import { batch, batchMap, batchParallel } from '@geodedb/client';

// Execute multiple queries in a batch
const summary = await client.batch([
  { query: "CREATE (n:Person {name: 'Alice'})" },
  { query: "CREATE (n:Person {name: 'Bob'})" },
  { query: 'MATCH (n:Person) RETURN count(n) AS cnt' },
]);

console.log(`${summary.successful}/${summary.total} queries succeeded`);
console.log('Total time:', summary.totalDurationMs, 'ms');

// Stop on first error
const summary2 = await client.batch(queries, { stopOnError: true });

// Execute in a transaction (all or nothing)
const summary3 = await client.batch(queries, { transaction: true });

// Map over data
const people = [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }];
await batchMap(conn, 'CREATE (n:Person {name: $name})', people);

Query Explain/Profile

import { explain, profile, formatPlan, formatProfile } from '@geodedb/client';

// Get query execution plan
const plan = await client.explain('MATCH (n:Person)-[:KNOWS]->(m) RETURN n, m');
console.log('Estimated cost:', plan.totalCost);
console.log('Estimated rows:', plan.totalRows);

// Format plan for display
console.log(formatPlan(plan));

// Profile actual execution
const prof = await client.profile('MATCH (n:Person) RETURN count(n)');
console.log('Execution time:', prof.totalTimeMs, 'ms');
console.log('Rows processed:', prof.totalRows);
console.log('Planning time:', prof.planningTimeMs, 'ms');
console.log('Memory used:', prof.memoryBytes, 'bytes');

// Format profile for display
console.log(formatProfile(prof));

Authentication Client

import { AuthClient } from '@geodedb/client';

const auth = await client.auth();

// User management
await auth.createUser('alice', { password: 'securePass123', roles: ['analyst'] });
await auth.changePassword('alice', 'newPassword456');
await auth.deactivateUser('alice');
const users = await auth.listUsers();

// Role management
await auth.createRole('analyst', {
  description: 'Data analyst role',
  permissions: [{ resource: 'NODE', action: 'READ' }],
});
await auth.assignRole('alice', 'analyst');
await auth.grantPermission('analyst', { resource: 'NODE', action: 'READ', label: 'Person' });

// Row-level security policies
await auth.createRLSPolicy(
  'tenant_isolation',
  'Document',
  'ALL',
  'n.tenant_id = current_user().tenant_id',
  { roles: ['user', 'analyst'] }
);
await auth.enableRLSPolicy('tenant_isolation', 'Document');

// Session info
const currentUser = await auth.currentUser();
const roles = await auth.currentRoles();
const canRead = await auth.hasPermission('NODE', 'READ', 'Person');

Abort/Cancellation

const controller = new AbortController();

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

try {
  const rows = await client.queryAll('MATCH (n) RETURN n', { signal: controller.signal });
} catch (e) {
  if (e.message.includes('Aborted')) {
    console.log('Query was cancelled');
  }
}

Connection Pool

const client = await createClient('quic://localhost:3141', {
  pooling: true,
  pool: {
    min: 2, // Minimum connections to maintain
    max: 10, // Maximum connections
    acquireTimeout: 30000, // Max wait time to acquire
    idleTimeout: 60000, // Close idle connections after
  },
});

// Check pool stats
console.log(client.poolStats);
// { total: 5, available: 3, inUse: 2, waiting: 0 }

API Reference

GeodeClient

MethodDescription
query(gql, options?)Execute query, return async iterator
queryAll(gql, options?)Execute query, return all rows
queryFirst(gql, options?)Get first row
queryScalar(gql, col, options?)Get single value
exec(gql, options?)Execute statement (no results)
withTransaction(fn)Execute in transaction
prepare(gql)Create prepared statement
explain(gql, options?)Get query execution plan
profile(gql, options?)Profile query execution
batch(queries, options?)Execute multiple queries
auth()Get authentication client
ping()Check connection health
close()Close client

Connection

MethodDescription
query(gql, options?)Execute query
exec(gql, options?)Execute statement
begin()Start transaction
prepare(gql)Create prepared statement
explain(gql, options?)Get query plan
profile(gql, options?)Profile execution
batch(queries, options?)Execute batch
ping()Health check
reset()Reset session
close()Close connection

Transaction

MethodDescription
query(gql, options?)Execute query
exec(gql, options?)Execute statement
savepoint(name)Create savepoint
rollbackTo(name)Rollback to savepoint
commit()Commit transaction
rollback()Rollback transaction

PreparedStatement

MethodDescription
execute(params?, options?)Execute, return iterator
executeAll(params?, options?)Execute, return all rows
exec(params?, options?)Execute (no results)
validate(params?)Validate parameters
close()Close statement
queryGet query text
parameterCountGet parameter count
namedParametersGet named parameter names

AuthClient

MethodDescription
createUser(name, options)Create user
deleteUser(name)Delete user
getUser(name)Get user info
listUsers()List all users
changePassword(name, password)Change password
activateUser(name)Activate user
deactivateUser(name)Deactivate user
createRole(name, options?)Create role
deleteRole(name)Delete role
assignRole(user, role)Assign role to user
revokeRole(user, role)Revoke role from user
grantPermission(role, perm)Grant permission
revokePermission(role, perm)Revoke permission
createRLSPolicy(...)Create RLS policy
deleteRLSPolicy(name, label)Delete RLS policy
currentUser()Get current user
currentRoles()Get current roles
hasPermission(res, action, label?)Check permission

Testing

# Run unit tests
npm test

# Run with coverage
npm run test:coverage

# Run integration tests (requires Docker)
npm run test:integration

Repository