ydb-sdk
Version:
Node.js bindings for working with YDB API over gRPC
645 lines (644 loc) • 23.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypedData = exports.identityConversion = exports.snakeToCamelCaseConversion = exports.TypedValues = exports.Types = exports.primitiveTypeToValue = exports.typeMetadataKey = void 0;
exports.declareType = declareType;
exports.convertYdbValueToNative = convertYdbValueToNative;
exports.getNameConverter = getNameConverter;
exports.withTypeOptions = withTypeOptions;
const lodash_1 = __importDefault(require("lodash"));
const long_1 = __importDefault(require("long"));
const ydb_sdk_proto_1 = require("ydb-sdk-proto");
require("reflect-metadata");
const luxon_1 = require("luxon");
const uuid_1 = require("./uuid");
const decimal_1 = require("./utils/decimal");
var Type = ydb_sdk_proto_1.Ydb.Type;
var PrimitiveTypeId = ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId;
var NullValue = ydb_sdk_proto_1.google.protobuf.NullValue;
exports.typeMetadataKey = Symbol('type');
function declareType(type) {
return Reflect.metadata(exports.typeMetadataKey, type);
}
exports.primitiveTypeToValue = {
[Type.PrimitiveTypeId.BOOL]: 'boolValue',
[Type.PrimitiveTypeId.INT8]: 'int32Value',
[Type.PrimitiveTypeId.UINT8]: 'uint32Value',
[Type.PrimitiveTypeId.INT16]: 'int32Value',
[Type.PrimitiveTypeId.UINT16]: 'uint32Value',
[Type.PrimitiveTypeId.INT32]: 'int32Value',
[Type.PrimitiveTypeId.UINT32]: 'uint32Value',
[Type.PrimitiveTypeId.INT64]: 'int64Value',
[Type.PrimitiveTypeId.UINT64]: 'uint64Value',
[Type.PrimitiveTypeId.FLOAT]: 'floatValue',
[Type.PrimitiveTypeId.DOUBLE]: 'doubleValue',
[Type.PrimitiveTypeId.STRING]: 'bytesValue',
[Type.PrimitiveTypeId.UTF8]: 'textValue',
[Type.PrimitiveTypeId.YSON]: 'bytesValue',
[Type.PrimitiveTypeId.JSON]: 'textValue',
[Type.PrimitiveTypeId.JSON_DOCUMENT]: 'textValue',
[Type.PrimitiveTypeId.DYNUMBER]: 'textValue',
[Type.PrimitiveTypeId.DATE]: 'uint32Value',
[Type.PrimitiveTypeId.DATETIME]: 'uint32Value',
[Type.PrimitiveTypeId.TIMESTAMP]: 'uint64Value',
[Type.PrimitiveTypeId.INTERVAL]: 'int64Value',
[Type.PrimitiveTypeId.TZ_DATE]: 'textValue',
[Type.PrimitiveTypeId.TZ_DATETIME]: 'textValue',
[Type.PrimitiveTypeId.TZ_TIMESTAMP]: 'textValue',
};
class Types {
static BOOL = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.BOOL };
static INT8 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.INT8 };
static UINT8 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.UINT8 };
static INT16 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.INT16 };
static UINT16 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.UINT16 };
static INT32 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.INT32 };
static UINT32 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.UINT32 };
static INT64 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.INT64 };
static UINT64 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.UINT64 };
static FLOAT = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.FLOAT };
static DOUBLE = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.DOUBLE };
static BYTES = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.STRING };
static UTF8 = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.UTF8 };
static TEXT = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.UTF8 };
static YSON = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.YSON };
static JSON = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.JSON };
static UUID = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.UUID };
static JSON_DOCUMENT = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.JSON_DOCUMENT };
static DATE = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.DATE };
static DATETIME = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.DATETIME };
static TIMESTAMP = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.TIMESTAMP };
static INTERVAL = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.INTERVAL };
static TZ_DATE = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.TZ_DATE };
static TZ_DATETIME = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.TZ_DATETIME };
static TZ_TIMESTAMP = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.TZ_TIMESTAMP };
static DYNUMBER = { typeId: ydb_sdk_proto_1.Ydb.Type.PrimitiveTypeId.DYNUMBER };
static VOID = { voidType: NullValue.NULL_VALUE };
static DEFAULT_DECIMAL = Types.decimal(22, 9);
static optional(type) {
return { optionalType: { item: type } };
}
/**
* A real number with the specified precision, up to 35 decimal digits
* @param precision Total number of decimal places (up to 35, inclusive).
* @param scale Number of places after the decimal point (out of the total number, meaning it can't be larger than the previous argument)
*/
static decimal(precision, scale) {
return { decimalType: { precision, scale } };
}
static tuple(...types) {
return { tupleType: { elements: types } };
}
static list(type) {
return { listType: { item: type } };
}
static struct(fields) {
return {
structType: {
members: Object.entries(fields).map(([name, type]) => ({ name, type })),
},
};
}
static dict(key, payload) {
return {
dictType: {
key,
payload,
},
};
}
static variant(type) {
if (type.structType) {
return {
variantType: {
structItems: type.structType,
},
};
}
if (type.tupleType) {
return {
variantType: {
tupleItems: type.tupleType,
},
};
}
throw new Error('Either tupleItems or structItems should be present in VariantType!');
}
}
exports.Types = Types;
class TypedValues {
static primitive(type, value) {
return {
type: type,
value: typeToValue(type, value),
};
}
static VOID = {
type: Types.VOID,
value: {
nullFlagValue: NullValue.NULL_VALUE,
},
};
static fromNative(type, value) {
return {
type,
value: typeToValue(type, value),
};
}
static bool(value) {
return TypedValues.primitive(Types.BOOL, value);
}
static int8(value) {
return TypedValues.primitive(Types.INT8, value);
}
static uint8(value) {
return TypedValues.primitive(Types.UINT8, value);
}
static int16(value) {
return TypedValues.primitive(Types.INT16, value);
}
static uint16(value) {
return TypedValues.primitive(Types.UINT16, value);
}
static int32(value) {
return TypedValues.primitive(Types.INT32, value);
}
static uint32(value) {
return TypedValues.primitive(Types.UINT32, value);
}
static int64(value) {
return TypedValues.primitive(Types.INT64, value);
}
static uint64(value) {
return TypedValues.primitive(Types.UINT64, value);
}
static float(value) {
return TypedValues.primitive(Types.FLOAT, value);
}
static double(value) {
return TypedValues.primitive(Types.DOUBLE, value);
}
static bytes(value) {
return TypedValues.primitive(Types.BYTES, value);
}
static utf8(value) {
return TypedValues.primitive(Types.UTF8, value);
}
static text(value) {
return TypedValues.primitive(Types.TEXT, value);
}
static yson(value) {
return TypedValues.primitive(Types.YSON, value);
}
static json(value) {
return TypedValues.primitive(Types.JSON, value);
}
static uuid(value) {
return TypedValues.primitive(Types.UUID, value);
}
static jsonDocument(value) {
return TypedValues.primitive(Types.JSON_DOCUMENT, value);
}
static date(value) {
return TypedValues.primitive(Types.DATE, value);
}
static datetime(value) {
return TypedValues.primitive(Types.DATETIME, value);
}
static timestamp(value) {
return TypedValues.primitive(Types.TIMESTAMP, value);
}
static interval(value) {
return TypedValues.primitive(Types.INTERVAL, value);
}
static tzDate(value) {
return TypedValues.primitive(Types.TZ_DATE, value);
}
static tzDatetime(value) {
return TypedValues.primitive(Types.TZ_DATETIME, value);
}
static tzTimestamp(value) {
return TypedValues.primitive(Types.TZ_TIMESTAMP, value);
}
static dynumber(value) {
return TypedValues.primitive(Types.DYNUMBER, value);
}
static optional(value) {
return {
type: {
optionalType: {
item: value.type,
},
},
value: value.value,
};
}
static optionalNull(type) {
return {
type: {
optionalType: {
item: type,
},
},
value: {
nullFlagValue: NullValue.NULL_VALUE,
},
};
}
static decimal(value, precision = 22, scale = 9) {
const type = Types.decimal(precision, scale);
return {
type,
value: typeToValue(type, value),
};
}
static tuple(...values) {
return {
type: {
tupleType: {
elements: values.map((v) => v.type).filter((t) => t),
},
},
value: {
items: values.map((v) => v.value).filter((v) => v),
},
};
}
static list(type, values) {
return {
type: {
listType: {
item: type,
},
},
value: {
items: values.map(value => typeToValue(type, value)),
},
};
}
static struct(fields, struct) {
const type = Types.struct(fields);
return {
type,
value: typeToValue(type, struct),
};
}
static dict(key, payload, dict) {
const type = Types.dict(key, payload);
return {
type,
value: typeToValue(type, dict),
};
}
}
exports.TypedValues = TypedValues;
const parseLong = (input) => {
let res = long_1.default.fromValue(input);
return res.high ? res : res.low;
};
const valueToNativeConverters = {
'boolValue': (input) => Boolean(input),
'int32Value': (input) => Number(input),
'uint32Value': (input) => Number(input),
'int64Value': (input) => parseLong(input),
'uint64Value': (input) => parseLong(input),
'floatValue': (input) => Number(input),
'doubleValue': (input) => Number(input),
'bytesValue': (input) => input,
'textValue': (input) => input,
'nullFlagValue': () => null,
};
function convertYdbValueToNative(type, value) {
// TODO: Performance may be increased if this logic will return simple light type converters based on type
if (type.typeId) {
if (type.typeId === PrimitiveTypeId.UUID) {
return (0, uuid_1.uuidToNative)(value);
}
const label = exports.primitiveTypeToValue[type.typeId];
if (!label) {
throw new Error(`Unknown PrimitiveTypeId: ${type.typeId}`);
}
const input = value[label];
return objectFromValue(type, valueToNativeConverters[label](input));
}
else if (type.decimalType) {
const high128 = value.high_128;
const low128 = value.low_128;
const scale = type.decimalType.scale;
return (0, decimal_1.toDecimalString)(high128, low128, scale);
}
else if (type.optionalType) {
const innerType = type.optionalType.item;
if (value.nullFlagValue === NullValue.NULL_VALUE) {
return null;
}
return convertYdbValueToNative(innerType, value);
}
else if (type.listType) {
const innerType = type.listType.item;
return lodash_1.default.map(value.items, (item) => convertYdbValueToNative(innerType, item));
}
else if (type.tupleType) {
const types = type.tupleType.elements;
const values = value.items;
return values.map((value, index) => convertYdbValueToNative(types[index], value));
}
else if (type.structType) {
const members = type.structType.members;
const items = value.items;
const struct = {};
items.forEach((item, index) => {
const member = members[index];
const memberName = member.name;
const memberType = member.type;
struct[memberName] = convertYdbValueToNative(memberType, item);
});
return struct;
}
else if (type.dictType) {
const keyType = type.dictType.key;
const payloadType = type.dictType.payload;
const dict = {};
value.pairs?.forEach((pair) => {
const nativeKey = convertYdbValueToNative(keyType, pair.key);
dict[nativeKey] = convertYdbValueToNative(payloadType, pair.payload);
});
return dict;
}
else if (type.variantType) {
if (type.variantType.tupleItems) {
const elements = type.variantType.tupleItems.elements;
const item = value.nestedValue;
const variantIndex = value.variantIndex;
return elements.map((element, index) => {
if (index === variantIndex) {
return convertYdbValueToNative(element, item);
}
return undefined;
});
}
else if (type.variantType.structItems) {
const members = type.variantType.structItems.members;
const item = value.nestedValue;
const variantIndex = value.variantIndex;
const variantType = members[variantIndex].type;
const variantName = members[variantIndex].name;
return {
[variantName]: convertYdbValueToNative(variantType, item),
};
}
else {
throw new Error('Either tupleItems or structItems should be present in VariantType!');
}
// } else if (type.taggedType) {
// // TODO: Enable in future versions of YDB
// const memberType = type.taggedType.type as IType
// const memberTag = type.taggedType.tag as string
// const res = convertYdbValueToNative(memberType, value)
// res.__proto__.tag = memberTag
}
else if (type.voidType === NullValue.NULL_VALUE) {
return null;
}
else {
throw new Error(`Unknown type ${JSON.stringify(type)}`);
}
}
function objectFromValue(type, value) {
const { typeId } = type;
switch (typeId) {
case PrimitiveTypeId.YSON:
return value.toString('utf8');
case PrimitiveTypeId.STRING:
return value;
case PrimitiveTypeId.DATE:
return new Date(value * 3600 * 1000 * 24);
case PrimitiveTypeId.DATETIME:
return new Date(value * 1000);
case PrimitiveTypeId.TIMESTAMP:
return new Date(value / 1000);
case PrimitiveTypeId.TZ_DATE:
case PrimitiveTypeId.TZ_DATETIME:
case PrimitiveTypeId.TZ_TIMESTAMP: {
const [datetime] = value.split(',');
return new Date(datetime + 'Z');
}
default:
return value;
}
}
function preparePrimitiveValue(type, value) {
if (type === Types.BYTES) {
return value instanceof Buffer ? value : Buffer.from(value);
}
const typeId = type.typeId;
switch (typeId) {
case PrimitiveTypeId.DATE:
return Number(value) / 3600 / 1000 / 24;
case PrimitiveTypeId.DATETIME:
return Number(value) / 1000;
case PrimitiveTypeId.TIMESTAMP:
return Number(value) * 1000;
case PrimitiveTypeId.TZ_DATE:
return luxon_1.DateTime.fromJSDate(value).toISODate() + ',GMT';
case PrimitiveTypeId.TZ_DATETIME:
return luxon_1.DateTime.fromJSDate(value, { zone: 'UTC' }).toFormat(`yyyy-MM-dd'T'HH:mm:ss',GMT'`);
case PrimitiveTypeId.TZ_TIMESTAMP:
return value.toISOString().replace('Z', '') + ',GMT';
default:
return value;
}
}
function typeToValue(type, value) {
if (!type) {
if (value) {
throw new Error(`Got no type while the value is ${value}`);
}
else {
throw new Error('Both type and value are empty');
}
}
else if (type.typeId) {
if (type.typeId === PrimitiveTypeId.UUID) {
return (0, uuid_1.uuidToValue)(value);
}
const valueLabel = exports.primitiveTypeToValue[type.typeId];
if (valueLabel) {
return { [valueLabel]: preparePrimitiveValue(type, value) };
}
else {
throw new Error(`Unknown PrimitiveTypeId: ${type.typeId}`);
}
}
else if (type.decimalType) {
const decimalValue = value;
const scale = type.decimalType.scale;
return (0, decimal_1.fromDecimalString)(decimalValue, scale);
}
else if (type.optionalType) {
const innerType = type.optionalType.item;
if (value !== undefined && value !== null) {
return typeToValue(innerType, value);
}
else {
return {
nullFlagValue: NullValue.NULL_VALUE
};
}
}
else if (type.listType) {
const listType = type.listType;
return {
items: lodash_1.default.map(value, (item) => typeToValue(listType.item, item))
};
}
else if (type.tupleType) {
const elements = type.tupleType.elements;
return {
items: lodash_1.default.map(value, (item, index) => typeToValue(elements[index], item))
};
}
else if (type.structType) {
const members = type.structType.members;
return {
items: lodash_1.default.map(members, (member) => {
const memberType = member.type;
const memberValue = value[member.name];
return typeToValue(memberType, memberValue);
}),
};
}
else if (type.dictType) {
const keyType = type.dictType.key;
const payloadType = type.dictType.payload;
return {
pairs: lodash_1.default.map(lodash_1.default.entries(value), ([key, value]) => ({
key: typeToValue(keyType, key),
payload: typeToValue(payloadType, value)
}))
};
}
else if (type.variantType) {
if (type.variantType.tupleItems) {
const elements = type.variantType.tupleItems.elements;
const variantIndex = value.findIndex((v) => v !== undefined);
return {
nestedValue: typeToValue(elements[variantIndex], value[variantIndex]),
variantIndex,
};
}
else if (type.variantType.structItems) {
const members = type.variantType.structItems.members;
const variantKey = Object.keys(value)[0];
const variantIndex = members.findIndex((a) => variantKey === a.name);
if (variantKey === undefined)
throw new Error("Variant type doesn't have not null fields");
return {
nestedValue: typeToValue(members[variantIndex].type, value[variantKey]),
variantIndex,
};
}
throw new Error('Either tupleItems or structItems should be present in VariantType!');
}
else if (type.voidType === NullValue.NULL_VALUE) {
return {
nullFlagValue: NullValue.NULL_VALUE,
};
}
else {
throw new Error(`Unknown type ${JSON.stringify(type)}`);
}
}
function getNameConverter(options, direction) {
return (options.namesConversion || exports.identityConversion)[direction];
}
function withTypeOptions(options) {
return function (constructor) {
return lodash_1.default.merge(constructor, { __options: options });
};
}
exports.snakeToCamelCaseConversion = {
jsToYdb: lodash_1.default.snakeCase,
ydbToJs: lodash_1.default.camelCase,
};
exports.identityConversion = {
jsToYdb: lodash_1.default.identity,
ydbToJs: lodash_1.default.identity,
};
class TypedData {
static __options = {};
constructor(data) {
lodash_1.default.assign(this, data);
}
getType(propertyKey) {
const typeMeta = Reflect.getMetadata(exports.typeMetadataKey, this, propertyKey);
if (!typeMeta) {
throw new Error(`Property ${propertyKey} should be decorated with @declareType!`);
}
return typeMeta;
}
getValue(propertyKey) {
const type = this.getType(propertyKey);
return typeToValue(type, this[propertyKey]);
}
getTypedValue(propertyKey) {
return {
type: this.getType(propertyKey),
value: this.getValue(propertyKey)
};
}
get typedProperties() {
return lodash_1.default.filter(Reflect.ownKeys(this), (key) => (typeof key === 'string' && Reflect.hasMetadata(exports.typeMetadataKey, this, key)));
}
getRowType() {
const cls = this.constructor;
const converter = getNameConverter(cls.__options, 'jsToYdb');
return {
structType: {
members: lodash_1.default.map(this.typedProperties, (propertyKey) => ({
name: converter(propertyKey),
type: this.getType(propertyKey)
}))
}
};
}
getRowValue() {
return {
items: lodash_1.default.map(this.typedProperties, (propertyKey) => {
return this.getValue(propertyKey);
})
};
}
static createNativeObjects(resultSet) {
const { rows, columns } = resultSet;
if (!columns) {
return [];
}
const converter = getNameConverter(this.__options, 'ydbToJs');
return lodash_1.default.map(rows, (row) => {
const obj = lodash_1.default.reduce(row.items, (acc, value, index) => {
const column = columns[index];
if (column.name && column.type) {
acc[converter(column.name)] = convertYdbValueToNative(column.type, value);
}
return acc;
}, {});
return new this(obj);
});
}
static asTypedCollection(collection) {
return {
type: {
listType: {
item: collection[0].getRowType()
}
},
value: {
items: collection.map((item) => item.getRowValue())
}
};
}
}
exports.TypedData = TypedData;