Working with YDB Types and Values
@ydbjs/value
provides type-safe conversion between JS and YDB:
- fromJs(js) — converts a JS value into a typed YDB value (type inference).
- toJs(ydb) — converts a YDB value back into native JS.
- Exposes type/value classes: Struct, List, Tuple, Dict, Optional, Null, primitives, etc.
Quick start
ts
import { fromJs } from '@ydbjs/value'
await sql`INSERT INTO users SELECT * FROM AS_TABLE(${[{ id: 1, name: 'Alice' }]})`
// Explicit wrapper (usually not required):
await sql`SELECT * FROM users WHERE meta = ${fromJs({ enabled: true })}`
Interpolations inside sql...
call fromJs automatically; explicit usage is rarely needed, mostly for strict typing control.
Optional (nullable)
In YDB, nullable fields are represented by Optional<T>
. In JS this is null
.
ts
import { Optional } from '@ydbjs/value'
// DON'T: pass bare null — YDB can't infer Optional<T> from JS null
// await sql`SELECT * FROM users WHERE middle_name = ${null}` // avoid this
// DO: wrap value with Optional carrying the explicit element type
// Example: middle_name has type Optional<Text>
import { Optional } from '@ydbjs/value'
import { TextType } from '@ydbjs/value/primitive'
await sql`SELECT * FROM users WHERE middle_name = ${new Optional(null, new TextType())}`
// Explicit Optional when you need to control field type:
await sql`SELECT * FROM users WHERE age = ${Optional.int32(null)}`
When generating table parameters from an array of objects, missing fields automatically become Optional in the resulting Struct.
Container types
- Optional — wrapper for nullable value with explicit element type.
- List — list of elements of the same type.
- Tuple — positional fixed-length tuple.
- Dict — key→value dictionary with explicit key/value types.
Types/values are built via Optional
, List
, Tuple
, Dict
and corresponding *Type
classes. Most types are inferred via fromJs
.
ts
// List<Struct>
await sql`INSERT INTO events SELECT * FROM AS_TABLE(${[
{ id: 1, payload: { ok: true } },
{ id: 2, payload: { ok: false } },
]})`
// Tuple, Dict — use fromJs when needed
Explicit type construction (when strict control is required):
ts
import { List, Struct, Optional } from '@ydbjs/value'
import { Int32Type, TextType } from '@ydbjs/value/primitive'
// Struct<{ id: Int32; name: Optional<Text> }>
const userType = new Struct({
id: new Int32Type(),
name: new Optional(null, new TextType()).type, // type only; set value separately
})
// List<Struct<...>> with values
const users = new List(
new Struct({ id: 1, name: new Optional(null, new TextType()) }, userType.type),
new Struct({ id: 2, name: new Optional('Bob', new TextType()) }, userType.type)
)
await sql`INSERT INTO users SELECT * FROM AS_TABLE(${users})`
Primitives and special types
ts
import { Uint64, Timestamp, Json } from '@ydbjs/value/primitive'
await sql`INSERT INTO t(id, ts, meta) VALUES (${new Uint64(1n)}, ${new Timestamp(new Date())}, ${new Json('{"foo":1}')})`
Native JS types (number, string, boolean, Date) with fromJs are typically enough; special classes help enforce format/range.
fromJs and toJs
ts
import { fromJs, toJs } from '@ydbjs/value'
const ydbVal = fromJs({ a: 1, b: [true, false] })
console.log(toJs(ydbVal)) // { a: 1, b: [true, false] }
Printing types (debug)
ts
import { typeToString } from '@ydbjs/value/print'
const q = sql`SELECT 1 AS a`
await q
console.log(typeToString(q.stats()?.resultSets?.[0]?.columns?.[0]?.type!))
Recommendations
- Use native JS values for parameters — the SDK wraps them into YDB Value.
- For complex structures pass plain objects/arrays — fromJs will build the type automatically.
- For nullable fields
null
is fine; use explicit Optional when strict control is needed. - Avoid mixing heterogeneous types within a single array, except for union-like struct merges (fields become Optional).