@ydbjs/core
v6.3.0
Published
Core driver for YDB: manages connections, endpoint discovery, authentication, and service client creation. Foundation for all YDB client operations.
Maintainers
Readme
@ydbjs/core
The @ydbjs/core package provides the core driver and connection management for YDB in JavaScript/TypeScript. It is the foundation for all YDB client operations, handling connection pooling, service client creation, authentication, and middleware.
Features
- Connection pooling and load balancing for YDB endpoints
- Service client creation for any YDB gRPC API
- Pluggable authentication via
@ydbjs/authproviders - Automatic endpoint discovery and failover
- TypeScript support with type definitions
- Compatible with Node.js and modern runtimes
Installation
npm install @ydbjs/coreHow It Works
- Driver: The main entry point. Manages connections, endpoint discovery, and authentication.
- Connection Pool: Maintains and balances gRPC channels to YDB endpoints.
- Service Clients: Use
driver.createClient(ServiceDefinition)to get a typed client for any YDB gRPC service (from@ydbjs/api). - Authentication: Pass a credentials provider from
@ydbjs/authto the driver for static, token, anonymous, or cloud metadata authentication. - Middleware: Internal middleware handles metadata, authentication, and debugging.
Usage
Basic Example
import { Driver } from '@ydbjs/core'
import { DiscoveryServiceDefinition } from '@ydbjs/api/discovery'
const driver = new Driver('grpc://localhost:2136/local')
await driver.ready()
const discovery = driver.createClient(DiscoveryServiceDefinition)
const endpoints = await discovery.listEndpoints({ database: '/local' })
console.log(endpoints)
await driver.close()Using Authentication Providers
import { Driver } from '@ydbjs/core'
import { StaticCredentialsProvider } from '@ydbjs/auth/static'
const driver = new Driver('grpc://localhost:2136/local', {
credentialsProvider: new StaticCredentialsProvider({
username: 'user',
password: 'pass',
}),
})
await driver.ready()
// ...You can also use AccessTokenCredentialsProvider, AnonymousCredentialsProvider, or MetadataCredentialsProvider from @ydbjs/auth.
Closing the Driver
Always close the driver when done to release resources:
driver.close()Observability via node:diagnostics_channel
@ydbjs/core publishes domain events over node:diagnostics_channel so external subscribers (@ydbjs/telemetry, OpenTelemetry, custom loggers) can build traces, metrics, and logs without the driver knowing anything about them.
Two primitives are used:
channel.publish— point-in-time state changes (gauges, counters, structured logs).tracingChannel.tracePromise— bracketed operations with duration and possible error (spans, latency histograms).
Conventions
All payloads share the same identity envelope, so multi-driver consumers can disambiguate:
type DriverIdentity = {
address: string // host:port the driver was constructed with
port: number | undefined
database: string // YDB database path
registeredAt: number // Date.now() at Driver construction
}Time values follow Node.js conventions:
- Durations are in milliseconds (
performance.now()deltas). - Timestamps are in epoch milliseconds (
Date.now()). - Subscribers that target OTel attributes / instruments (whose canonical unit is seconds) divide by 1000 at the mapping layer —
@ydbjs/telemetrydoes this for you.
Channels
Driver lifecycle
| Channel | Type | Payload |
| ------------------- | ------- | -------------------------------------------------------------------- |
| ydb:driver.ready | publish | { driver: DriverIdentity, duration: number } (ms since init) |
| ydb:driver.failed | publish | { driver: DriverIdentity, duration: number, error: unknown } (ms) |
| ydb:driver.closed | publish | { driver: DriverIdentity, uptime: number } (ms since ready) |
Discovery
| Channel | Type | Payload |
| -------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| tracing:ydb:driver.discovery | tracing | { driver: DriverIdentity } |
| ydb:driver.discovery.completed | publish | { driver: DriverIdentity, addedCount: number, removedCount: number, totalCount: number, duration: number } (ms)|
Connection pool
All connection-pool channels carry { driver: DriverIdentity, nodeId: bigint, address: string, location: string } plus the extra field listed below.
| Channel | Type | Extra fields |
| ------------------------------------ | ------- | ----------------------------------------------------------------------- |
| ydb:driver.connection.added | publish | (none) |
| ydb:driver.connection.pessimized | publish | until: number — epoch ms when the pessimization window ends |
| ydb:driver.connection.unpessimized | publish | duration: number — ms the connection actually stayed pessimized |
| ydb:driver.connection.retired | publish | reason: 'stale_active' \| 'stale_pessimized' |
| ydb:driver.connection.removed | publish | reason: 'replaced' \| 'idle' \| 'pool_close' |
The pool exposes two distinct teardown events for connections:
retired— the connection was removed from active routing (e.g. its endpoint disappeared from discovery), but its gRPC channel is left open so in-flight streams can drain.removed— the gRPC channel was physically closed. Thereasonfield distinguishes whether it was a replacement, an idle teardown, or a pool shutdown.
A gauge of "alive channels" can be reconstructed from the delta between connection.added and connection.removed. A gauge of "routable connections" should also subtract retired. @ydbjs/telemetry does this with an in-memory Map<DriverIdentity, ConnectionState>.
Subscribing
import { channel, tracingChannel } from 'node:diagnostics_channel'
channel('ydb:driver.ready').subscribe((msg) => {
console.log('driver ready', msg)
})
tracingChannel('tracing:ydb:driver.discovery').subscribe({
start(ctx) {
// ctx.driver.database === '/local'
},
asyncEnd(ctx) {
// discovery round succeeded
},
error(ctx) {
// ctx.error is the failure
},
})⚠️ Subscribers must be safe
node:diagnostics_channel invokes subscribers synchronously, on the publishing thread. Any exception thrown inside a subscriber propagates up the call stack and will disrupt the SDK — a buggy subscriber can break a Driver.ready(), abort a discovery round, or leak a gRPC channel.
@ydbjs/core does not wrap your subscribers. It is your responsibility to keep them safe:
channel('ydb:driver.ready').subscribe((msg) => {
try {
metrics.driverReady.add(1, { database: msg.driver.database })
} catch (err) {
// Never let a metrics failure escape — log it locally and move on.
console.error('telemetry subscriber failed', err)
}
})The same applies to tracingChannel handlers (start, asyncEnd, error, etc.) — each must be self-contained and never throw.
Stability
Channel names and payload field names follow semantic versioning. Adding new optional fields is a minor change; renaming or removing fields is a major change. Treat the channel names and payload shapes as a public API surface.
Development
Building the Package
npm run buildRunning Tests
npm testFor watch mode during development:
npm run test:watchLicense
This project is licensed under the Apache 2.0 License.
