nestjs-protobuf-es
Version:
Protobuf-ES integration for NestJS
146 lines (145 loc) • 5.72 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.initMessage = initMessage;
exports.populate = populate;
exports.populateInPlace = populateInPlace;
const protobuf_1 = require("@bufbuild/protobuf");
const reflect_1 = require("@bufbuild/protobuf/reflect");
const wkt_1 = require("@bufbuild/protobuf/wkt");
const valid_types_1 = require("./valid-types");
/**
* Create a new message instance.
*
* This is similar to `create` from `@bufbuild/protobuf`, with the difference that it handles JS dates and converts them to `Timestamp`.
*/
function initMessage(schema, init) {
if ((0, protobuf_1.isMessage)(init, schema)) {
return init;
}
if (init == null) {
return (0, protobuf_1.create)(schema);
}
if (schema.typeName === 'google.protobuf.Timestamp') {
if (init instanceof Date) {
return (0, wkt_1.timestampFromDate)(init);
}
return (0, protobuf_1.create)(schema, init);
}
// biome-ignore lint/suspicious/noExplicitAny: `any` is the best choice for dynamic access
const target = init;
for (const member of schema.members) {
let field;
// biome-ignore lint/suspicious/noExplicitAny: `any` is the best choice for dynamic access
let value;
if (member.kind === 'field') {
field = member;
value = target[field.localName];
}
else {
const caseName = target[member.localName]?.case;
if (caseName === undefined)
continue;
field = member.fields.find((f) => f.localName === caseName);
value = target[member.localName].value;
if (!field)
continue;
}
if (value === undefined)
continue;
if (field.fieldKind === 'message') {
const msg = initMessage(field.message, value);
if (field.oneof) {
target[field.oneof.localName].value = msg;
}
else {
target[field.localName] = msg;
}
}
else if (field.fieldKind === 'map') {
if (field.mapKind !== 'message' || typeof value !== 'object' || value === null)
continue;
for (const key of Object.keys(value)) {
value[key] = initMessage(field.message, value[key]);
}
}
else if (field.fieldKind === 'list') {
if (field.listKind !== 'message' || !Array.isArray(value) || value.length === 0)
continue;
for (let i = 0; i < value.length; i++) {
value[i] = initMessage(field.message, value[i]);
}
}
}
return (0, protobuf_1.create)(schema, target);
}
/**
* Create a deep copy of the message and populate it and/or convert `Timestamp` fields into JS `Date`.
*
* This method may be slow because of the cloning.
* If you don't worry about modifying the original message, you can use `populateInPlace` instead.
*/
function populate(schema, message, options) {
return populateInPlace(schema, (0, protobuf_1.clone)(schema, message), options);
}
/**
* Deep populate the message and/or convert `Timestamp` fields into JS `Date`.
*
* This method is more performant, but mutates the original message.
*/
function populateInPlace(schema, message, options) {
const r = (0, reflect_1.isReflectMessage)(message) ? message : (0, reflect_1.reflect)(schema, message, false);
if (schema.typeName === 'google.protobuf.Timestamp') {
if (options?.jsDates) {
return (0, wkt_1.timestampDate)(r.message);
}
// Timestamp already has every field populated.
return r.message;
}
for (const member of r.members) {
let field;
if (member.kind === 'field') {
field = member;
}
else {
const oneofField = r.oneofCase(member);
if (!oneofField)
continue;
field = oneofField;
}
if (field.fieldKind === 'message') {
if (options?.messages ||
(options?.jsDates && r.isSet(field)) ||
(options?.validTypes !== false && (0, valid_types_1.isFieldRequired)(field))) {
r.set(field, populateInPlace(field.message, r.get(field), options));
}
}
else {
// biome-ignore lint/suspicious/noExplicitAny: `any` is the best choice for dynamic access
const target = r.message;
const value = field.oneof
? target[field.oneof.localName].value
: target[field.localName];
if (field.fieldKind === 'scalar') {
if (value === undefined && options?.scalars) {
r.set(field, (0, reflect_1.scalarZeroValue)(field.scalar, field.longAsString));
}
}
else if (field.fieldKind === 'enum') {
if (value === undefined && options?.scalars) {
r.set(field, field.enum.values[0].number);
}
}
else if ((field.fieldKind === 'map' && field.mapKind === 'message') ||
(field.fieldKind === 'list' && field.listKind === 'message')) {
if (options?.validTypes !== false || options.messages || options.jsDates) {
const mapOrList = r.get(field);
for (const entry of mapOrList.entries()) {
const msg = populateInPlace(field.message, entry[1], options);
mapOrList.set(entry[0], msg);
}
}
}
}
}
return r.message;
}