Работа с типами YDB и пакетом @ydbjs/value
@ydbjs/value отвечает за типобезопасную конвертацию значений между JS и YDB:
- fromJs(js) — конвертирует JS‑значение в типизированное YDB‑значение (инференс типа).
- toJs(ydb) — преобразует YDB‑значение обратно в нативный JS.
- Экспортирует классы типов и значений: Struct, List, Tuple, Dict, Optional, Null, примитивы и др.
Быстрый старт
import { fromJs } from '@ydbjs/value'
await sql`INSERT INTO users SELECT * FROM AS_TABLE(${[{ id: 1, name: 'Alice' }]})`
// Явная обёртка (обычно не требуется):
await sql`SELECT * FROM users WHERE meta = ${fromJs({ enabled: true })}`
Интерполяции в sql...
автоматически вызывают fromJs, поэтому явное использование нужно редко — в основном для контроля типа.
Optional (nullable)
В YDB nullable поля представлены типом Optional<T>
. В JS это null
.
import { Optional } from '@ydbjs/value'
// НЕЛЬЗЯ: передавать null без явного типа — из JS null невозможно понять YDB‑тип Optional над каким именно T
// await sql`SELECT * FROM users WHERE middle_name = ${null}` // так делать не нужно
// Делайте так: указывайте тип явно, оборачивая значение в Optional с явным типом
// Пример: middle_name имеет тип 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())}`
// Явный Optional, если нужно управлять типом поля:
await sql`SELECT * FROM users WHERE age = ${Optional.int32(null)}`
При генерации табличных параметров из массива объектов отсутствующие поля автоматически становятся Optional в результирующем Struct.
Контейнерные типы
- Optional — обёртка для nullable‑значения с явным типом элемента.
- List — список элементов одного типа.
- Tuple — позиционный кортеж фиксированной длины.
- Dict — словарь ключ→значение с типами для ключа и значения.
Типы и значения задаются через классы Optional
, List
, Tuple
, Dict
и соответствующие *Type
. В большинстве случаев тип выводится автоматически через fromJs
.
// List<Struct>
await sql`INSERT INTO events SELECT * FROM AS_TABLE(${[
{ id: 1, payload: { ok: true } },
{ id: 2, payload: { ok: false } },
]})`
// Tuple, Dict — создаются через fromJs при необходимости
Явное построение типов (когда нужен точный контроль):
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, // только тип, значение зададим отдельно
})
// List<Struct<...>> со значениями
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})`
Примитивы и специальные типы
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}')})`
Обычно достаточно нативных JS типов (number, string, boolean, Date) и fromJs — он корректно выведет типы. Специальные классы пригодятся для явного контроля формата/диапазона.
fromJs и toJs
import { fromJs, toJs } from '@ydbjs/value'
const ydbVal = fromJs({ a: 1, b: [true, false] })
console.log(toJs(ydbVal)) // { a: 1, b: [true, false] }
Печать типов (debug)
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!))
Рекомендации
- Для параметров используйте нативные JS значения — SDK сам обернёт их в YDB Value.
- Для сложных структур передавайте plain объекты/массивы — fromJs построит тип автоматически.
- Для nullable полей достаточно null; явный Optional используйте при жёстком контроле типа.
- Избегайте смешения гетерогенных типов в одном массиве, кроме ожидаемого кейса объединения структур (поля станут Optional).