🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@bufbuild/protobuf

Package Overview
Dependencies
Maintainers
5
Versions
76
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bufbuild/protobuf - npm Package Compare versions

Comparing version
2.12.1
to
2.13.0
+35
dist/commonjs/txtpb/from-text.d.ts
import { type DescMessage } from "../descriptors.js";
import type { Registry } from "../registry.js";
import type { MessageShape } from "../types.js";
/**
* Options for parsing the protobuf text format.
*/
export interface TextReadOptions {
/**
* The registry to resolve `google.protobuf.Any` and extensions. Parsing an
* Any in its expanded form, or an extension field, requires it.
*/
registry?: Registry | undefined;
/**
* The maximum depth of nested messages to parse. A message nesting deeper
* than this fails with an error instead of exhausting the call stack.
* Defaults to 100.
*/
recursionLimit: number;
}
/**
* Parse a message from the protobuf text format.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export declare function fromText<Desc extends DescMessage>(schema: Desc, text: string, options?: Partial<TextReadOptions>): MessageShape<Desc>;
/**
* Parse a message from the protobuf text format, merging into the target.
*
* Repeated fields are appended, singular fields are overwritten (last wins),
* message fields are merged, and map entries are added (overwriting an existing
* key).
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export declare function mergeFromText<Desc extends DescMessage>(schema: Desc, target: MessageShape<Desc>, text: string, options?: Partial<TextReadOptions>): MessageShape<Desc>;
"use strict";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.fromText = fromText;
exports.mergeFromText = mergeFromText;
const descriptors_js_1 = require("../descriptors.js");
const proto_int64_js_1 = require("../proto-int64.js");
const reflect_js_1 = require("../reflect/reflect.js");
const error_js_1 = require("../reflect/error.js");
const scalar_js_1 = require("../reflect/scalar.js");
const to_binary_js_1 = require("../to-binary.js");
const extensions_js_1 = require("../extensions.js");
const text_encoding_js_1 = require("../wire/text-encoding.js");
const reader_js_1 = require("./reader.js");
const is_group_like_js_1 = require("./is-group-like.js");
function makeReadContext(reader, options) {
return Object.assign(Object.assign({ recursionLimit: 100 }, options), { reader, depth: 0 });
}
/**
* Parse a message from the protobuf text format.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
function fromText(schema, text, options) {
const msg = (0, reflect_js_1.reflect)(schema);
parseText(msg, text, options);
return msg.message;
}
/**
* Parse a message from the protobuf text format, merging into the target.
*
* Repeated fields are appended, singular fields are overwritten (last wins),
* message fields are merged, and map entries are added (overwriting an existing
* key).
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
function mergeFromText(schema, target, text, options) {
parseText((0, reflect_js_1.reflect)(schema, target), text, options);
return target;
}
function parseText(msg, text, options) {
if (!proto_int64_js_1.protoInt64.supported) {
throw new Error("the protobuf text format requires BigInt, which is unavailable in this environment");
}
const ctx = makeReadContext(new reader_js_1.Reader(text), options);
try {
readMessageBody(msg, ctx, "eof");
}
catch (e) {
if ((0, error_js_1.isFieldError)(e)) {
throw new Error(`cannot decode ${e.field()} from text format: ${e.message}`,
// @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces
{ cause: e });
}
throw e;
}
}
/**
* Read a message body (its fields until `close`), guarding nesting depth. The
* top-level message and every nested "{...}"/"<...>" block go through here, so
* the recursion limit counts the root too, matching fromJson and fromBinary.
*/
function readMessageBody(msg, ctx, close) {
if (++ctx.depth > ctx.recursionLimit) {
throw new Error(`recursion limit of ${ctx.recursionLimit} reached decoding ${msg.desc}`);
}
readFields(msg, ctx, close);
ctx.depth--;
}
/**
* Read the fields of a message until `close` (the matching close token, or
* "eof" for the top-level message).
*/
function readFields(msg, ctx, close) {
const seen = { fields: new Set(), oneofs: new Set() };
for (;;) {
const tok = ctx.reader.peek();
if (tok.type === "eof") {
if (close !== "eof") {
throw new Error(`unexpected end of input, expected "${close}"`);
}
return;
}
if (close !== "eof" && tok.type === close) {
ctx.reader.next();
return;
}
readField(msg, ctx, seen);
// A single optional "," or ";" may follow a field. Because the next
// iteration treats a separator as a field name and rejects it, a doubled
// separator is an error, while a single trailing one is allowed.
consumeSeparator(ctx);
}
}
function readField(msg, ctx, seen) {
var _a;
const nameTok = ctx.reader.next();
if (nameTok.type === "identifier") {
const field = fieldByTextName(msg.desc, nameTok.value);
if (field !== undefined) {
checkSeen(field, seen);
readFieldValue(msg, field, ctx);
return;
}
// Reserved field names are silently skipped; any other unknown name is an
// error. This matches protobuf-go.
if (msg.desc.proto.reservedName.includes(nameTok.value)) {
skipFieldValue(ctx);
return;
}
throw new Error(`unknown field "${nameTok.value}" for ${msg.desc}`);
}
if (nameTok.type === "[") {
const name = ctx.reader.readTypeName();
// Inside google.protobuf.Any, a bracketed name is always a type URL; in any
// other message it is an extension name.
if (msg.desc.typeName === "google.protobuf.Any") {
readExpandedAny(msg, ctx, name, seen);
return;
}
const ext = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getExtension(name);
if (ext !== undefined && ext.extendee.typeName === msg.desc.typeName) {
checkSeen(ext, seen);
readExtensionField(msg, ext, ctx);
return;
}
throw new Error(`unknown extension "[${name}]" for ${msg.desc}`);
}
if (nameTok.type === "int") {
// Like protobuf-go, a field cannot be addressed by number, so the numbered
// output of printUnknownFields cannot be read back.
throw new Error(`cannot specify field by number: ${nameTok.text}`);
}
throw new Error(`expected a field name, got ${describe(nameTok)}`);
}
// Rejects a repeated occurrence of a singular field, or a second member of the
// same oneof. Repeated and map fields may appear any number of times.
function checkSeen(field, seen) {
if (field.fieldKind === "list" || field.fieldKind === "map") {
return;
}
if (field.oneof !== undefined) {
if (seen.oneofs.has(field.oneof)) {
throw new Error(`oneof "${field.oneof.name}" is already set`);
}
seen.oneofs.add(field.oneof);
}
if (seen.fields.has(field.number)) {
const what = field.kind === "extension"
? `extension "[${field.typeName}]"`
: `field "${field.name}"`;
throw new Error(`non-repeated ${what} is repeated`);
}
seen.fields.add(field.number);
}
function readFieldValue(target, field, ctx) {
// The ":" separator is optional before a message, group, or map value, but
// required for scalars, enums, and lists of them.
const hasColon = consumeColon(ctx);
if (!colonOptional(field) && !hasColon) {
throw new Error(`expected ":" before value of field "${field.name}"`);
}
switch (field.fieldKind) {
case "scalar":
target.set(field, readScalarValue(field, field.scalar, ctx));
break;
case "enum":
target.set(field, readEnumValue(field.enum, ctx));
break;
case "message": {
const sub = target.isSet(field)
? target.get(field)
: (0, reflect_js_1.reflect)(field.message);
readMessageValue(sub, ctx);
target.set(field, sub);
break;
}
case "list":
readListField(field, target.get(field), ctx);
break;
case "map":
readMapField(field, target.get(field), ctx);
break;
}
}
function readExtensionField(msg, ext, ctx) {
// Extensions live in the unknown-field set. We read the new value into a
// container seeded with any existing value, so a repeated extension appends.
const existing = (0, extensions_js_1.hasExtension)(msg.message, ext)
? (0, extensions_js_1.getExtension)(msg.message, ext)
: undefined;
const [container, field, get] = (0, extensions_js_1.createExtensionContainer)(ext, existing);
readFieldValue(container, field, ctx);
(0, extensions_js_1.setExtension)(msg.message, ext, get());
}
function colonOptional(field) {
return (field.fieldKind === "message" ||
field.fieldKind === "map" ||
(field.fieldKind === "list" && field.listKind === "message"));
}
/**
* Read a "{ ... }" or "< ... >" block into the given message.
*/
function readMessageValue(msg, ctx) {
readMessageBody(msg, ctx, readMessageOpen(ctx));
}
function readMessageOpen(ctx) {
const open = ctx.reader.next();
if (open.type === "{") {
return "}";
}
if (open.type === "<") {
return ">";
}
throw new Error(`expected "{" or "<", got ${describe(open)}`);
}
/**
* Read a repeated value: either a single element, or a bracketed list
* "[ e, e, ... ]". This is the one place the list grammar lives, so list
* fields, map fields, and the reserved-skip path cannot drift in how they
* accept (and reject) separators.
*/
function readBracketedList(ctx, readElement) {
if (ctx.reader.peek().type !== "[") {
readElement();
return;
}
ctx.reader.next(); // "["
if (ctx.reader.peek().type === "]") {
ctx.reader.next();
return;
}
for (;;) {
readElement();
const sep = ctx.reader.next();
if (sep.type === "]") {
return;
}
if (sep.type !== ",") {
throw new Error(`expected "," or "]" in list, got ${describe(sep)}`);
}
}
}
function readListField(field, list, ctx) {
readBracketedList(ctx, () => list.add(readListItem(field, ctx)));
}
function readListItem(field, ctx) {
switch (field.listKind) {
case "scalar":
return readScalarValue(field, field.scalar, ctx);
case "enum":
return readEnumValue(field.enum, ctx);
case "message": {
const sub = (0, reflect_js_1.reflect)(field.message);
readMessageValue(sub, ctx);
return sub;
}
}
}
function readMapField(field, map, ctx) {
readBracketedList(ctx, () => readMapEntry(field, map, ctx));
}
/**
* Read a map entry: a "{ key: ... value: ... }" block. A missing key or value
* defaults to the zero value, like protobuf-go. A duplicate "key" or "value"
* within one entry is an error; duplicate keys across separate entries are
* legal, with the last entry winning (handled by the caller's map.set).
*/
function readMapEntry(field, map, ctx) {
if (++ctx.depth > ctx.recursionLimit) {
throw new Error(`recursion limit of ${ctx.recursionLimit} reached decoding a map entry`);
}
const close = readMessageOpen(ctx);
let key = (0, scalar_js_1.scalarZeroValue)(field.mapKey, false);
let value = mapValueZero(field);
let keySeen = false;
let valueSeen = false;
for (;;) {
const tok = ctx.reader.peek();
if (tok.type === close) {
ctx.reader.next();
break;
}
if (tok.type === "eof") {
throw new Error(`unexpected end of input, expected "${close}"`);
}
const nameTok = ctx.reader.next();
if (nameTok.type !== "identifier") {
throw new Error(`expected "key" or "value", got ${describe(nameTok)}`);
}
if (nameTok.value === "key") {
if (keySeen) {
throw new Error('map entry "key" is already set');
}
keySeen = true;
requireColon(ctx);
key = readScalarValue(field, field.mapKey, ctx);
}
else if (nameTok.value === "value") {
if (valueSeen) {
throw new Error('map entry "value" is already set');
}
valueSeen = true;
value = readMapValue(field, ctx);
}
else {
throw new Error(`unknown field "${nameTok.value}" in map entry`);
}
consumeSeparator(ctx);
}
ctx.depth--;
map.set(key, value);
}
function readMapValue(field, ctx) {
switch (field.mapKind) {
case "scalar":
requireColon(ctx);
return readScalarValue(field, field.scalar, ctx);
case "enum":
requireColon(ctx);
return readEnumValue(field.enum, ctx);
case "message": {
consumeColon(ctx);
const sub = (0, reflect_js_1.reflect)(field.message);
readMessageValue(sub, ctx);
return sub;
}
}
}
function mapValueZero(field) {
switch (field.mapKind) {
case "scalar":
return (0, scalar_js_1.scalarZeroValue)(field.scalar, false);
case "enum":
return field.enum.values[0].number;
case "message":
return (0, reflect_js_1.reflect)(field.message);
}
}
/**
* Read `google.protobuf.Any` in its expanded form `[type.url]: { ... }`.
*
* The expanded form is mutually exclusive with the raw `type_url` (field 1) and
* `value` (field 2) fields, and may appear only once. We enforce that through
* the same seen-set the duplicate-field check uses: the expansion is rejected
* if either field is already set, and it marks both as set so a following
* `type_url` or `value` is rejected too.
*/
function readExpandedAny(msg, ctx, typeUrl, seen) {
var _a;
if (seen.fields.has(1) || seen.fields.has(2)) {
throw new Error("google.protobuf.Any cannot mix the expanded form with type_url/value");
}
const slash = typeUrl.lastIndexOf("/");
const typeName = slash >= 0 ? typeUrl.substring(slash + 1) : typeUrl;
const desc = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getMessage(typeName);
if (desc === undefined) {
throw new Error(`unable to resolve "${typeUrl}" for google.protobuf.Any`);
}
consumeColon(ctx);
const unpacked = (0, reflect_js_1.reflect)(desc);
readMessageValue(unpacked, ctx);
const any = msg.message;
// Preserve the exact type URL, including any custom domain prefix.
any.typeUrl = typeUrl;
any.value = (0, to_binary_js_1.toBinary)(desc, unpacked.message);
seen.fields.add(1);
seen.fields.add(2);
}
// Consume an optional leading "-" sign and report whether one was present.
// This sees a sign token only before a number (the scanner glues a sign onto an
// identifier as in "-inf"), and whitespace and comments between the sign and the
// number are insignificant, so "- 42" means -42, matching protobuf-go.
function consumeSign(ctx) {
if (ctx.reader.peek().type === "-") {
ctx.reader.next();
return true;
}
return false;
}
function readScalarValue(field, type, ctx) {
const negative = consumeSign(ctx);
const tok = ctx.reader.next();
switch (type) {
case descriptors_js_1.ScalarType.STRING:
case descriptors_js_1.ScalarType.BYTES: {
if (negative) {
throw new Error("a string value cannot have a sign");
}
if (tok.type !== "string") {
throw new Error(`expected a string, got ${describe(tok)}`);
}
const bytes = concatStrings(tok, ctx);
if (type === descriptors_js_1.ScalarType.BYTES) {
return bytes;
}
try {
return (0, text_encoding_js_1.getTextEncoding)().decodeUtf8(bytes, field.utf8Validation);
}
catch (_a) {
throw new Error("invalid UTF-8 in string");
}
}
case descriptors_js_1.ScalarType.BOOL:
return readBoolValue(tok, negative);
case descriptors_js_1.ScalarType.FLOAT:
// Round to 32-bit precision: an out-of-range value becomes ±inf, which is
// what the text format requires for float overflow.
return Math.fround(readFloatValue(tok, negative));
case descriptors_js_1.ScalarType.DOUBLE:
return readFloatValue(tok, negative);
case descriptors_js_1.ScalarType.UINT32:
case descriptors_js_1.ScalarType.FIXED32:
return Number(readUnsignedInt(tok, negative));
case descriptors_js_1.ScalarType.UINT64:
case descriptors_js_1.ScalarType.FIXED64:
return readUnsignedInt(tok, negative);
case descriptors_js_1.ScalarType.INT64:
case descriptors_js_1.ScalarType.SINT64:
case descriptors_js_1.ScalarType.SFIXED64:
return readSignedInt(tok, negative);
default:
// INT32, SINT32, SFIXED32: the reflect layer range-checks the number.
return Number(readSignedInt(tok, negative));
}
}
function readEnumValue(descEnum, ctx) {
const negative = consumeSign(ctx);
const tok = ctx.reader.next();
if (tok.type === "identifier") {
if (negative) {
throw new Error(`invalid enum value "-${tok.value}" for ${descEnum}`);
}
const value = descEnum.values.find((v) => v.name === tok.value);
if (value === undefined) {
throw new Error(`unknown enum value "${tok.value}" for ${descEnum}`);
}
return value.number;
}
if (tok.type === "int") {
// The reflect layer validates the number: any int32 for open enums, a known
// value for closed (proto2) enums.
return Number(readSignedInt(tok, negative));
}
throw new Error(`expected an enum value for ${descEnum}, got ${describe(tok)}`);
}
function readBoolValue(tok, negative) {
if (!negative) {
if (tok.type === "identifier") {
switch (tok.value) {
case "true":
case "True":
case "t":
return true;
case "false":
case "False":
case "f":
return false;
}
}
if (tok.type === "int") {
const value = intTokenToBigInt(tok);
if (value === BigInt(0)) {
return false;
}
if (value === BigInt(1)) {
return true;
}
}
}
throw new Error(`expected a bool, got ${negative ? "-" : ""}${describe(tok)}`);
}
function readFloatValue(tok, negative) {
if (tok.type === "identifier") {
// A separate "-" token (negative) before a float literal is invalid; the
// only signed literals are "-inf"/"-infinity", which the scanner glues into
// one identifier token. "-nan" is not a literal, so it falls through to the
// error below. This matches protobuf-go's identifier-path sign handling.
if (negative) {
throw new Error(`invalid float value "-${tok.value}"`);
}
switch (tok.value.toLowerCase()) {
case "inf":
case "infinity":
return Number.POSITIVE_INFINITY;
case "-inf":
case "-infinity":
return Number.NEGATIVE_INFINITY;
case "nan":
return Number.NaN;
}
throw new Error(`invalid float value "${tok.value}"`);
}
if (tok.type === "float") {
const n = Number(tok.text);
return negative ? -n : n;
}
if (tok.type === "int") {
// Octal and hexadecimal literals are not valid for float and double fields.
if (tok.base !== 10) {
throw new Error("octal and hexadecimal are not valid for a float field");
}
const n = Number(tok.text);
return negative ? -n : n;
}
throw new Error(`expected a float, got ${describe(tok)}`);
}
function readSignedInt(tok, negative) {
if (tok.type !== "int") {
throw new Error(`expected an integer, got ${describe(tok)}`);
}
const value = intTokenToBigInt(tok);
return negative ? -value : value;
}
function readUnsignedInt(tok, negative) {
// Reject any sign for an unsigned field, including "-0": the reflect layer
// would silently accept it as 0.
if (negative) {
throw new Error("an unsigned field does not accept a negative value");
}
return readSignedInt(tok, false);
}
function intTokenToBigInt(tok) {
// Octal text keeps its leading "0" (e.g. "0755"), which BigInt would read as
// decimal, so it needs the "0o" prefix. Hex ("0x...") and decimal text are
// accepted by BigInt as-is.
return tok.base === 8
? BigInt("0o" + tok.text.substring(1))
: BigInt(tok.text);
}
// Concatenate adjacent string literals into a single byte string.
function concatStrings(first, ctx) {
if (ctx.reader.peek().type !== "string") {
return first.value;
}
const parts = [first.value];
let length = first.value.length;
while (ctx.reader.peek().type === "string") {
const tok = ctx.reader.next();
parts.push(tok.value);
length += tok.value.length;
}
const bytes = new Uint8Array(length);
let offset = 0;
for (const part of parts) {
bytes.set(part, offset);
offset += part.length;
}
return bytes;
}
/**
* Skip the value of a reserved field. Like every other nested read, the message
* case is guarded by the recursion limit.
*/
function skipFieldValue(ctx) {
consumeColon(ctx);
skipValue(ctx);
}
function skipValue(ctx) {
readBracketedList(ctx, () => skipSingleValue(ctx));
}
function skipSingleValue(ctx) {
const tok = ctx.reader.peek();
if (tok.type === "{" || tok.type === "<") {
skipMessageBlock(ctx);
return;
}
// A leading sign is consumed leniently here: skipping a reserved value should
// tolerate "- 5".
consumeSign(ctx);
const value = ctx.reader.next();
if (value.type === "string") {
while (ctx.reader.peek().type === "string") {
ctx.reader.next();
}
return;
}
if (value.type !== "identifier" &&
value.type !== "int" &&
value.type !== "float") {
throw new Error(`expected a value, got ${describe(value)}`);
}
}
function skipMessageBlock(ctx) {
if (++ctx.depth > ctx.recursionLimit) {
throw new Error(`recursion limit of ${ctx.recursionLimit} reached skipping a reserved field`);
}
const close = readMessageOpen(ctx);
for (;;) {
const tok = ctx.reader.peek();
if (tok.type === close) {
ctx.reader.next();
ctx.depth--;
return;
}
if (tok.type === "eof") {
throw new Error(`unexpected end of input, expected "${close}"`);
}
const nameTok = ctx.reader.next();
if (nameTok.type === "[") {
ctx.reader.readTypeName();
}
else if (nameTok.type !== "identifier" && nameTok.type !== "int") {
throw new Error(`expected a field name, got ${describe(nameTok)}`);
}
skipFieldValue(ctx);
consumeSeparator(ctx);
}
}
// Consume an optional "," or ";" that separates fields or list/map elements.
function consumeSeparator(ctx) {
const sep = ctx.reader.peek();
if (sep.type === "," || sep.type === ";") {
ctx.reader.next();
}
}
function consumeColon(ctx) {
if (ctx.reader.peek().type === ":") {
ctx.reader.next();
return true;
}
return false;
}
function requireColon(ctx) {
if (!consumeColon(ctx)) {
throw new Error(`expected ":", got ${describe(ctx.reader.peek())}`);
}
}
const textFieldCache = new WeakMap();
/**
* Resolve a field by its text format name, mirroring protobuf-go's ByTextName:
* group-like fields are addressed by their message type name, with the
* lowercase form as an alias; JSON names are not in this table.
*/
function fieldByTextName(desc, name) {
let byText = textFieldCache.get(desc);
if (byText === undefined) {
byText = new Map();
for (const field of desc.fields) {
if ((0, is_group_like_js_1.isGroupLike)(field)) {
setOnce(byText, field.message.name, field);
setOnce(byText, field.message.name.toLowerCase(), field);
}
else {
setOnce(byText, field.name, field);
}
}
textFieldCache.set(desc, byText);
}
return byText.get(name);
}
function setOnce(map, key, field) {
if (!map.has(key)) {
map.set(key, field);
}
}
function describe(tok) {
switch (tok.type) {
case "identifier":
return `"${tok.value}"`;
case "int":
case "float":
return `"${tok.text}"`;
case "string":
return "a string";
case "eof":
return "end of input";
default:
return `"${tok.type}"`;
}
}
export { toText } from "./to-text.js";
export type { TextWriteOptions } from "./to-text.js";
export { fromText, mergeFromText } from "./from-text.js";
export type { TextReadOptions } from "./from-text.js";
"use strict";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeFromText = exports.fromText = exports.toText = void 0;
var to_text_js_1 = require("./to-text.js");
Object.defineProperty(exports, "toText", { enumerable: true, get: function () { return to_text_js_1.toText; } });
var from_text_js_1 = require("./from-text.js");
Object.defineProperty(exports, "fromText", { enumerable: true, get: function () { return from_text_js_1.fromText; } });
Object.defineProperty(exports, "mergeFromText", { enumerable: true, get: function () { return from_text_js_1.mergeFromText; } });
import type { DescField, DescMessage } from "../descriptors.js";
/**
* Returns true if the field is structured like a proto2 group: a delimited
* message field whose name is the lowercase of its message type name, declared
* in the same scope as that message.
*
* The text format addresses such fields by their message type name (e.g.
* `MyGroup`) rather than their field name. This is a faithful port of
* protobuf-go's isGroupLike (internal/filedesc/desc.go), so editions delimited
* fields are treated exactly like proto2 groups.
*
* Testing `field.message` first narrows the DescField union to its three
* message-bearing variants (singular, list, and map value) — all of which carry
* `delimitedEncoding` — so it is in scope below without a cast. Maps are
* excluded automatically, because their `delimitedEncoding` is always false.
*/
export declare function isGroupLike(field: DescField): field is DescField & {
message: DescMessage;
};
"use strict";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.isGroupLike = isGroupLike;
/**
* Returns true if the field is structured like a proto2 group: a delimited
* message field whose name is the lowercase of its message type name, declared
* in the same scope as that message.
*
* The text format addresses such fields by their message type name (e.g.
* `MyGroup`) rather than their field name. This is a faithful port of
* protobuf-go's isGroupLike (internal/filedesc/desc.go), so editions delimited
* fields are treated exactly like proto2 groups.
*
* Testing `field.message` first narrows the DescField union to its three
* message-bearing variants (singular, list, and map value) — all of which carry
* `delimitedEncoding` — so it is in scope below without a cast. Maps are
* excluded automatically, because their `delimitedEncoding` is always false.
*/
function isGroupLike(field) {
// Groups are always delimited-encoded message fields.
if (field.message === undefined || !field.delimitedEncoding) {
return false;
}
// Group fields are always named after the lowercase message type name.
if (field.message.name.toLowerCase() !== field.name) {
return false;
}
// Groups can only be defined in the file they are used in.
if (field.message.file !== field.parent.file) {
return false;
}
// Group messages are always defined in the same scope as the field.
return field.message.parent === field.parent;
}
/**
* A lexical token of the protobuf text format.
*
* This is a discriminated union keyed by `type`: punctuation tokens carry no
* payload, identifiers carry their text, string tokens carry their decoded
* bytes (the same bytes back both string and bytes fields, and bytes fields may
* hold sequences that are not valid UTF-8), and numbers carry their literal
* text plus enough classification for the parser to accept or reject them per
* field type.
*
* The minus sign before a number is its own token rather than part of the
* number, which keeps numeric sign handling in one place in the parser and,
* because whitespace and comments between tokens are insignificant, makes
* `- 42` mean `-42`, matching protobuf-go (decode_number.go). A minus glued to
* a letter is instead folded into a negative identifier (`-inf`/`-infinity`),
* because protobuf-go requires the sign glued for those literals — `- inf` is
* an error there, not negative infinity.
*/
export type Token = {
readonly type: Structural | "eof";
} | {
readonly type: "identifier";
readonly value: string;
} | {
readonly type: "string";
readonly value: Uint8Array;
} | {
readonly type: "int";
readonly text: string;
readonly base: 8 | 10 | 16;
} | {
readonly type: "float";
readonly text: string;
};
/**
* The structural tokens, each the literal source character it represents.
*/
type Structural = "{" | "}" | "<" | ">" | "[" | "]" | ":" | "," | ";" | "-";
/**
* A tokenizer for the protobuf text format.
*
* The parser drives it with `peek()` and `next()` (one-token lookahead) and,
* once it knows it is in a field-name position, asks for the contents of a
* bracketed name with `readTypeName()` — the `[...]` syntax for extensions and
* Any type URLs is ambiguous with the list syntax at the lexical level. The
* structure is modeled on the graphql-js lexer: a single scan position and a
* per-token reader that returns the decoded value.
*/
export declare class Reader {
private readonly input;
private readonly length;
private pos;
private lookahead;
constructor(input: string);
/**
* Return the next token without consuming it.
*/
peek(): Token;
/**
* Consume and return the next token.
*/
next(): Token;
/**
* Read the contents of a bracketed name, used for extension fields and the
* expanded form of google.protobuf.Any. The opening `[` must already have
* been consumed with `next()`. Whitespace and comments inside the brackets
* are insignificant. Returns the inner name with the brackets removed, e.g.
* "pkg.Message.field" or "type.googleapis.com/pkg.Message".
*
* The text format grammar for this is incomplete, so we follow protobuf-go's
* parseTypeName: the prefix may contain URL characters, `/` separators, and
* well-formed percent-escapes, and the type name after the last `/` must be a
* dotted identifier.
*/
readTypeName(): string;
private scan;
private skipSpace;
private scanIdentifier;
/**
* Scan a numeric literal. The sign is a separate token, so a number never
* starts with `-`. The literal must end at a delimiter, so `10f` is a float
* but `10bar`, `1.2.3`, `09`, and `0xZ` are errors.
*/
private scanNumber;
private expectDelimiter;
private scanString;
private scanEscape;
private takeWhile;
private charAt;
}
export {};
"use strict";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.Reader = void 0;
const text_encoding_js_1 = require("../wire/text-encoding.js");
const tokenEof = { type: "eof" };
/**
* A tokenizer for the protobuf text format.
*
* The parser drives it with `peek()` and `next()` (one-token lookahead) and,
* once it knows it is in a field-name position, asks for the contents of a
* bracketed name with `readTypeName()` — the `[...]` syntax for extensions and
* Any type URLs is ambiguous with the list syntax at the lexical level. The
* structure is modeled on the graphql-js lexer: a single scan position and a
* per-token reader that returns the decoded value.
*/
class Reader {
constructor(input) {
this.pos = 0;
// A leading byte-order mark is insignificant; skip it like protobuf-go's
// tokenizer does.
this.input = input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
this.length = this.input.length;
}
/**
* Return the next token without consuming it.
*/
peek() {
if (this.lookahead === undefined) {
this.lookahead = this.scan();
}
return this.lookahead;
}
/**
* Consume and return the next token.
*/
next() {
const tok = this.peek();
this.lookahead = undefined;
return tok;
}
/**
* Read the contents of a bracketed name, used for extension fields and the
* expanded form of google.protobuf.Any. The opening `[` must already have
* been consumed with `next()`. Whitespace and comments inside the brackets
* are insignificant. Returns the inner name with the brackets removed, e.g.
* "pkg.Message.field" or "type.googleapis.com/pkg.Message".
*
* The text format grammar for this is incomplete, so we follow protobuf-go's
* parseTypeName: the prefix may contain URL characters, `/` separators, and
* well-formed percent-escapes, and the type name after the last `/` must be a
* dotted identifier.
*/
readTypeName() {
let name = "";
for (;;) {
this.skipSpace();
const c = this.charAt(this.pos);
if (c === undefined) {
throw new Error("unterminated [...] name");
}
if (c === "]") {
this.pos++;
break;
}
if (c === "/") {
name += "/";
this.pos++;
}
else if (c === "%") {
if (!isHexDigit(this.charAt(this.pos + 1)) ||
!isHexDigit(this.charAt(this.pos + 2))) {
throw new Error("invalid percent-escape in [...] name");
}
name += this.input.substring(this.pos, this.pos + 3);
this.pos += 3;
}
else if (isUrlChar(c)) {
name += c;
this.pos++;
}
else {
throw new Error(`unexpected ${quoteChar(c)} in [...] name`);
}
}
validateTypeName(name);
return name;
}
scan() {
this.skipSpace();
const c = this.charAt(this.pos);
if (c === undefined) {
return tokenEof;
}
switch (c) {
case "{":
case "}":
case "<":
case ">":
case "[":
case "]":
case ":":
case ",":
case ";":
this.pos++;
return { type: c };
case "-":
// A "-" glued to a letter begins a negative identifier (-inf or
// -infinity); otherwise it is a sign token. A number may have whitespace
// between the sign and the digits, so the sign is a separate token the
// parser reassembles; a float literal may not, matching protobuf-go,
// where inf/infinity parse through the identifier path with the sign
// glued (so "- inf" is an error but "- 42" is -42).
if (isLetter(this.charAt(this.pos + 1))) {
return this.scanIdentifier();
}
this.pos++;
return { type: "-" };
case '"':
case "'":
return this.scanString(c);
}
if (isDigit(c)) {
return this.scanNumber();
}
if (c === "." && isDigit(this.charAt(this.pos + 1))) {
return this.scanNumber();
}
if (isLetter(c)) {
return this.scanIdentifier();
}
throw new Error(`unexpected ${quoteChar(c)}`);
}
skipSpace() {
for (;;) {
const c = this.input[this.pos];
if (c === " " ||
c === "\t" ||
c === "\n" ||
c === "\r" ||
c === "\v" ||
c === "\f") {
this.pos++;
}
else if (c === "#") {
// A comment runs to the end of the line.
this.pos++;
while (this.pos < this.length && this.input[this.pos] !== "\n") {
this.pos++;
}
}
else {
return;
}
}
}
scanIdentifier() {
const start = this.pos;
if (this.charAt(this.pos) === "-") {
this.pos++; // a glued negative identifier such as -inf
}
this.pos++; // the first letter (the caller guarantees one is present)
while (isLetterOrDigit(this.charAt(this.pos))) {
this.pos++;
}
return { type: "identifier", value: this.input.substring(start, this.pos) };
}
/**
* Scan a numeric literal. The sign is a separate token, so a number never
* starts with `-`. The literal must end at a delimiter, so `10f` is a float
* but `10bar`, `1.2.3`, `09`, and `0xZ` are errors.
*/
scanNumber() {
var _a, _b, _c, _d;
const start = this.pos;
if (this.input[this.pos] === "0" &&
/[xX]/.test((_a = this.charAt(this.pos + 1)) !== null && _a !== void 0 ? _a : "")) {
// Hexadecimal: `0x` followed by one or more hex digits.
this.pos += 2;
const digits = this.pos;
while (isHexDigit(this.charAt(this.pos))) {
this.pos++;
}
if (this.pos === digits) {
throw new Error("invalid hexadecimal literal");
}
this.expectDelimiter();
return {
type: "int",
text: this.input.substring(start, this.pos),
base: 16,
};
}
if (this.input[this.pos] === "0" &&
isOctalDigit(this.charAt(this.pos + 1))) {
// Octal: a leading `0` followed by octal digits. A subsequent non-octal
// digit (as in `078`) ends the run, and the delimiter check rejects it.
this.pos++;
while (isOctalDigit(this.charAt(this.pos))) {
this.pos++;
}
this.expectDelimiter();
return {
type: "int",
text: this.input.substring(start, this.pos),
base: 8,
};
}
// A decimal integer or a floating point literal. A leading "0" stands
// alone (octal and hex were handled above), so the delimiter check below
// rejects a following digit — `08` and `09` are malformed, not decimal.
let isFloat = false;
if (this.charAt(this.pos) === "0") {
this.pos++;
}
else {
while (isDigit(this.charAt(this.pos))) {
this.pos++;
}
}
if (this.charAt(this.pos) === ".") {
isFloat = true;
this.pos++;
while (isDigit(this.charAt(this.pos))) {
this.pos++;
}
}
if (/[eE]/.test((_b = this.charAt(this.pos)) !== null && _b !== void 0 ? _b : "")) {
isFloat = true;
this.pos++;
if (/[+-]/.test((_c = this.charAt(this.pos)) !== null && _c !== void 0 ? _c : "")) {
this.pos++;
}
const digits = this.pos;
while (isDigit(this.charAt(this.pos))) {
this.pos++;
}
if (this.pos === digits) {
throw new Error("invalid exponent");
}
}
// A trailing `f`/`F` marks a float and is not part of the value passed to
// Number(); capture the end before consuming it so it stays out of the text.
const end = this.pos;
if (/[fF]/.test((_d = this.charAt(this.pos)) !== null && _d !== void 0 ? _d : "")) {
isFloat = true;
this.pos++;
}
this.expectDelimiter();
const text = this.input.substring(start, end);
return isFloat ? { type: "float", text } : { type: "int", text, base: 10 };
}
// A number must be terminated by a delimiter — any character that cannot
// continue a name or number. This rejects `09`, `0xZ`, `1.2.3`, and `5bar`.
expectDelimiter() {
const c = this.charAt(this.pos);
if (c !== undefined &&
(isLetterOrDigit(c) || c === "-" || c === "+" || c === ".")) {
throw new Error("invalid number");
}
}
scanString(quote) {
this.pos++; // opening quote
const bytes = [];
// Literal characters are accumulated as a run and encoded as UTF-8 in one
// batch when the run ends (at an escape or the closing quote). Escapes
// contribute their bytes directly.
let runStart = this.pos;
for (;;) {
const c = this.charAt(this.pos);
if (c === undefined) {
throw new Error("unterminated string");
}
if (c === quote) {
pushUtf8(bytes, this.input.substring(runStart, this.pos));
this.pos++; // closing quote
return { type: "string", value: new Uint8Array(bytes) };
}
if (c === "\\") {
pushUtf8(bytes, this.input.substring(runStart, this.pos));
this.pos++; // backslash
this.scanEscape(bytes);
runStart = this.pos;
continue;
}
// A raw newline or NUL is not allowed in a string, matching protobuf-go.
if (c === "\n" || c === "\0") {
throw new Error(`invalid ${quoteChar(c)} in string`);
}
this.pos++;
}
}
scanEscape(bytes) {
const c = this.charAt(this.pos);
if (c === undefined) {
throw new Error("unterminated escape sequence");
}
switch (c) {
case '"':
case "'":
case "\\":
case "?":
bytes.push(c.charCodeAt(0));
this.pos++;
return;
case "a":
bytes.push(0x07);
this.pos++;
return;
case "b":
bytes.push(0x08);
this.pos++;
return;
case "f":
bytes.push(0x0c);
this.pos++;
return;
case "n":
bytes.push(0x0a);
this.pos++;
return;
case "r":
bytes.push(0x0d);
this.pos++;
return;
case "t":
bytes.push(0x09);
this.pos++;
return;
case "v":
bytes.push(0x0b);
this.pos++;
return;
case "x": {
this.pos++;
const hex = this.takeWhile(isHexDigit, 2);
if (hex.length === 0) {
throw new Error("invalid hex escape \\x");
}
bytes.push(parseInt(hex, 16));
return;
}
case "u":
case "U": {
this.pos++;
const width = c === "u" ? 4 : 8;
const hex = this.takeWhile(isHexDigit, width);
if (hex.length !== width) {
throw new Error(`invalid unicode escape \\${c}`);
}
const code = parseInt(hex, 16);
// Reject surrogate code points and values beyond U+10FFFF. We
// deliberately do NOT combine an adjacent `\u` low surrogate into a
// pair the way protobuf-go does: the conformance suite (the
// StringLiteral*Surrogate* cases) requires every surrogate escape, lone
// or paired, to be a parse error. Do not "fix" this toward Go.
if (code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
throw new Error(`invalid unicode escape \\${c}${hex}`);
}
pushUtf8(bytes, String.fromCodePoint(code));
return;
}
default:
if (isOctalDigit(c)) {
const oct = this.takeWhile(isOctalDigit, 3);
const value = parseInt(oct, 8);
if (value > 0xff) {
throw new Error(`octal escape \\${oct} out of range`);
}
bytes.push(value);
return;
}
throw new Error(`invalid escape \\${c}`);
}
}
// Consume up to `max` consecutive characters matching `pred` and return them.
takeWhile(pred, max) {
const start = this.pos;
while (this.pos < start + max && pred(this.charAt(this.pos))) {
this.pos++;
}
return this.input.substring(start, this.pos);
}
charAt(index) {
return index < this.length ? this.input[index] : undefined;
}
}
exports.Reader = Reader;
/**
* Validate a type name (and URL prefix) from a bracketed name, mirroring
* protobuf-go's parseTypeName. The type name is everything after the last `/`;
* the prefix before it is a URL that may carry extra characters and
* percent-escapes, but must not begin with `/`.
*/
function validateTypeName(name) {
const lastSlash = name.lastIndexOf("/");
if (lastSlash >= 0 && name[0] === "/") {
throw new Error("invalid type name: empty URL host");
}
const typeName = name.substring(lastSlash + 1);
if (typeName.length === 0) {
throw new Error("invalid type name: empty");
}
for (const part of typeName.split(".")) {
if (part.length === 0) {
throw new Error("invalid type name: empty component");
}
}
for (const c of typeName) {
if (!(isLetterOrDigit(c) || c === "." || c === "-")) {
throw new Error(`unexpected ${quoteChar(c)} in type name`);
}
}
}
function isDigit(c) {
return c !== undefined && c >= "0" && c <= "9";
}
function isOctalDigit(c) {
return c !== undefined && c >= "0" && c <= "7";
}
function isHexDigit(c) {
return (c !== undefined &&
((c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F")));
}
function isLetter(c) {
return (c !== undefined &&
((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_"));
}
function isLetterOrDigit(c) {
return isLetter(c) || isDigit(c);
}
/**
* A character permitted in the URL prefix of an Any type name, matching
* protobuf-go's isUrlChar plus the type-name characters.
*/
function isUrlChar(c) {
return (isLetterOrDigit(c) ||
c === "-" ||
c === "." ||
c === "~" ||
c === "!" ||
c === "$" ||
c === "&" ||
c === "(" ||
c === ")" ||
c === "*" ||
c === "+" ||
c === "," ||
c === ";" ||
c === "=");
}
function quoteChar(c) {
var _a;
const code = (_a = c.codePointAt(0)) !== null && _a !== void 0 ? _a : 0;
return code >= 0x20 && code <= 0x7e
? `"${c}"`
: `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
}
// Append the UTF-8 encoding of `text` to `out`, using the same Text Encoding
// API as the rest of the library. Unpaired surrogates become U+FFFD (the
// standard TextEncoder behavior), matching binary serialization.
function pushUtf8(out, text) {
if (text.length === 0) {
return;
}
for (const byte of (0, text_encoding_js_1.getTextEncoding)().encodeUtf8(text)) {
out.push(byte);
}
}
import { type DescMessage } from "../descriptors.js";
import type { Registry } from "../registry.js";
import type { MessageShape } from "../types.js";
/**
* Options for serializing to the protobuf text format.
*
* The text format represents 64-bit integral types with BigInt and has no
* string fall-back: toText throws immediately when BigInt is unavailable.
*/
export interface TextWriteOptions {
/**
* Print unknown fields?
*
* Disabled by default. This is a debugging aid only: unknown fields are
* printed by field number, and fromText rejects fields named by number, so
* output that includes them cannot be parsed back.
*/
printUnknownFields: boolean;
/**
* The registry to resolve `google.protobuf.Any` and extensions. Without it,
* an Any is written as its raw `type_url`/`value` fields and extensions are
* omitted.
*/
registry?: Registry | undefined;
}
/**
* Serialize a message to the protobuf text format.
*
* The output matches the default formatting of txtpbfmt: two-space indentation,
* one field per line, and a trailing newline.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export declare function toText<Desc extends DescMessage>(schema: Desc, message: MessageShape<Desc>, options?: Partial<TextWriteOptions>): string;
"use strict";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.toText = toText;
const descriptors_js_1 = require("../descriptors.js");
const proto_int64_js_1 = require("../proto-int64.js");
const reflect_js_1 = require("../reflect/reflect.js");
const extensions_js_1 = require("../extensions.js");
const index_js_1 = require("../wire/index.js");
const index_js_2 = require("../wkt/index.js");
const is_group_like_js_1 = require("./is-group-like.js");
const writer_js_1 = require("./writer.js");
const textWriteDefaults = {
printUnknownFields: false,
};
function makeWriteOptions(options) {
return options ? Object.assign(Object.assign({}, textWriteDefaults), options) : textWriteDefaults;
}
// A bound on nested unknown-field rendering. The known-field tree is a finite,
// valid in-memory message and needs no limit, but printUnknownFields re-parses
// bytes and recurses (the length-delimited and group heuristics), so we cap
// that path as defense-in-depth.
const unknownFieldDepthLimit = 100;
/**
* Serialize a message to the protobuf text format.
*
* The output matches the default formatting of txtpbfmt: two-space indentation,
* one field per line, and a trailing newline.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
function toText(schema, message, options) {
if (!proto_int64_js_1.protoInt64.supported) {
throw new Error("the protobuf text format requires BigInt, which is unavailable in this environment");
}
const writer = new writer_js_1.Writer();
writeMessage(writer, (0, reflect_js_1.reflect)(schema, message), makeWriteOptions(options));
return writer.toString();
}
/**
* Write the body of a message: regular fields in declaration order, then
* resolvable extensions sorted by full name, then unknown fields by number
* (only when printUnknownFields is enabled). For `google.protobuf.Any`, the
* expanded form replaces all of this.
*/
function writeMessage(writer, msg, opts) {
var _a;
if (writeAny(writer, msg, opts)) {
return;
}
for (const field of msg.fields) {
// Unset fields are omitted, including unset required fields; like
// protobuf-go, we do not validate required fields when serializing.
if (msg.isSet(field)) {
writeField(writer, fieldTextName(field), field, msg, opts);
}
}
const extensionNumbers = writeExtensions(writer, msg, opts);
if (opts.printUnknownFields) {
for (const field of (_a = msg.getUnknown()) !== null && _a !== void 0 ? _a : []) {
if (!extensionNumbers.has(field.no)) {
writeUnknownField(writer, field, 0);
}
}
}
}
function writeField(writer, name, field, msg, opts) {
// Narrowing on fieldKind lets msg.get(field) return the precise reflect type
// for each case — ReflectMessage, ReflectList, ReflectMap, number, or a scalar
// value — so none of the branches need a cast.
switch (field.fieldKind) {
case "scalar":
writer.scalar(name, scalarToText(field.scalar, msg.get(field)));
break;
case "enum":
writer.scalar(name, enumToText(field.enum, msg.get(field)));
break;
case "message":
writeMessageValue(writer, name, msg.get(field), opts);
break;
case "list":
writeList(writer, name, field, msg.get(field), opts);
break;
case "map":
writeMap(writer, name, field, msg.get(field), opts);
break;
}
}
/**
* Write a message value as `name: { ... }`, or `name: {}` when it has no body.
* The body is rendered speculatively and rolled back if it turns out empty.
*/
function writeMessageValue(writer, name, msg, opts) {
const mark = writer.mark();
writer.openMessage(name);
writeMessage(writer, msg, opts);
if (writer.writesSince(mark) === 1) {
// Only the opener was written, so the message is empty.
writer.reset(mark);
writer.emptyMessage(name);
}
else {
writer.end();
}
}
function writeList(writer, name, field, list, opts) {
switch (field.listKind) {
case "scalar":
for (const item of list) {
writer.scalar(name, scalarToText(field.scalar, item));
}
break;
case "enum":
for (const item of list) {
writer.scalar(name, enumToText(field.enum, item));
}
break;
case "message":
for (const item of list) {
writeMessageValue(writer, name, item, opts);
}
break;
}
}
function writeMap(writer, name, field, map, opts) {
// Map entries are emitted in iteration (insertion) order; unlike protobuf-go,
// we deliberately do not sort them.
for (const [key, value] of map) {
writer.openMessage(name);
writer.scalar("key", scalarToText(field.mapKey, key));
switch (field.mapKind) {
case "scalar":
writer.scalar("value", scalarToText(field.scalar, value));
break;
case "enum":
writer.scalar("value", enumToText(field.enum, value));
break;
case "message":
writeMessageValue(writer, "value", value, opts);
break;
}
writer.end();
}
}
/**
* Write `google.protobuf.Any` in its expanded form `[type.url]: { ... }`.
* Returns false (so the generic path writes `type_url`/`value` instead) when
* the message is not an Any, has no type URL, or the type cannot be resolved.
*/
function writeAny(writer, msg, opts) {
if (msg.desc.typeName !== "google.protobuf.Any" ||
opts.registry === undefined) {
return false;
}
const any = msg.message;
if (any.typeUrl === "") {
return false;
}
const unpacked = (0, index_js_2.anyUnpack)(any, opts.registry);
if (unpacked === undefined) {
return false;
}
const desc = opts.registry.getMessage(unpacked.$typeName);
if (desc === undefined) {
return false;
}
// The bracketed name preserves the exact type URL, including a custom domain.
writeMessageValue(writer, "[" + any.typeUrl + "]", (0, reflect_js_1.reflect)(desc, unpacked), opts);
return true;
}
/**
* Write resolvable extensions, sorted by full name, and return their field
* numbers so writeMessage does not also emit them as raw unknown fields.
*/
function writeExtensions(writer, msg, opts) {
const numbers = new Set();
const unknown = msg.getUnknown();
if (opts.registry === undefined || unknown === undefined) {
return numbers;
}
const extensions = [];
for (const { no } of unknown) {
if (numbers.has(no)) {
continue;
}
const extension = opts.registry.getExtensionFor(msg.desc, no);
if (extension !== undefined) {
numbers.add(no);
extensions.push(extension);
}
}
extensions.sort((a, b) => a.typeName < b.typeName ? -1 : a.typeName > b.typeName ? 1 : 0);
for (const extension of extensions) {
const value = (0, extensions_js_1.getExtension)(msg.message, extension);
const [container, field] = (0, extensions_js_1.createExtensionContainer)(extension, value);
writeField(writer, "[" + extension.typeName + "]", field, container, opts);
}
return numbers;
}
/**
* Write an unknown field by its field number, mirroring protobuf-go: varints as
* decimal, fixed-width values as hexadecimal, length-delimited data as a nested
* message when it parses cleanly as one and a quoted byte string otherwise, and
* groups recursively.
*/
function writeUnknownField(writer, field, depth) {
const name = field.no.toString();
const reader = new index_js_1.BinaryReader(field.data);
switch (field.wireType) {
case index_js_1.WireType.Varint:
writer.scalar(name, reader.uint64().toString());
break;
case index_js_1.WireType.Bit32:
writer.scalar(name, "0x" + (reader.fixed32() >>> 0).toString(16).padStart(8, "0"));
break;
case index_js_1.WireType.Bit64:
writer.scalar(name, "0x" + BigInt(reader.fixed64()).toString(16).padStart(16, "0"));
break;
case index_js_1.WireType.LengthDelimited: {
const bytes = reader.bytes();
const nested = depth < unknownFieldDepthLimit ? parseUnknownMessage(bytes) : undefined;
if (nested === undefined) {
writer.scalar(name, (0, writer_js_1.quoteBytes)(bytes));
}
else {
writeUnknownGroup(writer, name, nested, depth);
}
break;
}
case index_js_1.WireType.StartGroup: {
const fields = [];
while (reader.pos < reader.len) {
const [no, wireType] = reader.tag();
if (wireType === index_js_1.WireType.EndGroup) {
break;
}
fields.push({ no, wireType, data: reader.skip(wireType, no) });
}
writeUnknownGroup(writer, name, fields, depth);
break;
}
}
}
function writeUnknownGroup(writer, name, fields, depth) {
if (fields.length === 0) {
writer.emptyMessage(name);
return;
}
writer.openMessage(name);
for (const field of fields) {
writeUnknownField(writer, field, depth + 1);
}
writer.end();
}
/**
* Try to interpret length-delimited bytes as a nested message. Returns its
* unknown fields if the bytes parse cleanly and completely, otherwise undefined
* (in which case the data is rendered as a quoted byte string).
*/
function parseUnknownMessage(bytes) {
if (bytes.length === 0) {
return undefined;
}
const reader = new index_js_1.BinaryReader(bytes);
const fields = [];
try {
while (reader.pos < reader.len) {
const [no, wireType] = reader.tag();
if (no <= 0 || wireType === index_js_1.WireType.EndGroup) {
return undefined;
}
fields.push({ no, wireType, data: reader.skip(wireType, no) });
}
}
catch (_a) {
return undefined;
}
return reader.pos === reader.len ? fields : undefined;
}
/**
* The name a field is addressed by in the text format: a group-like (delimited)
* field uses its message type name, every other field its proto name.
*/
function fieldTextName(field) {
return (0, is_group_like_js_1.isGroupLike)(field) ? field.message.name : field.name;
}
function scalarToText(type, value) {
switch (type) {
case descriptors_js_1.ScalarType.STRING:
return (0, writer_js_1.quoteString)(value);
case descriptors_js_1.ScalarType.BYTES:
return (0, writer_js_1.quoteBytes)(value);
case descriptors_js_1.ScalarType.BOOL:
return value === true ? "true" : "false";
case descriptors_js_1.ScalarType.FLOAT:
return floatToText(value, true);
case descriptors_js_1.ScalarType.DOUBLE:
return floatToText(value, false);
default:
// All integer types print as decimal with no prefix. 64-bit values are
// bigint; String() gives the decimal form for both bigint and number.
return String(value);
}
}
function enumToText(descEnum, value) {
// Emit the first-declared name for a value, so allow_alias enums match
// protobuf-go (the by-number record can resolve to a non-first alias). An
// unknown value prints as a decimal.
for (const v of descEnum.values) {
if (v.number === value) {
return v.name;
}
}
return value.toString();
}
function floatToText(value, single) {
// Round to 32-bit precision first so an overflow becomes inf (not the JS
// "Infinity") and the value is the true 32-bit value before we test it.
const n = single ? Math.fround(value) : value;
if (Number.isNaN(n)) {
return "nan";
}
if (n === Number.POSITIVE_INFINITY) {
return "inf";
}
if (n === Number.NEGATIVE_INFINITY) {
return "-inf";
}
if (Object.is(n, -0)) {
return "-0";
}
if (!single) {
// Number.prototype.toString already yields the shortest decimal that
// round-trips to the same 64-bit value.
return n.toString();
}
// For 32-bit floats, find the shortest decimal that round-trips to the same
// float32, mirroring strconv.AppendFloat(n, 'g', -1, 32) in protobuf-go.
for (let precision = 1; precision <= 9; precision++) {
const candidate = Number(n.toPrecision(precision));
if (Math.fround(candidate) === n) {
return candidate.toString();
}
}
return n.toString();
}
/**
* A position in the Writer's output, used to roll back speculative writes.
*/
interface Mark {
readonly size: number;
readonly depth: number;
}
/**
* A writer for the protobuf text format.
*
* The Writer owns layout: indentation, line breaks, and braces. Its output
* matches the default formatting of txtpbfmt and the multi-line output of
* protobuf-go: two-space indentation, `name: value` with a single space after
* the colon, submessages as `name: {` with the body indented and `}` aligned
* under the field name, and a trailing newline. It never inserts the randomized
* extra spaces that protobuf-go adds to discourage parsing its output as
* canonical.
*
* Output accumulates into a single buffer of lines, so a deep tree costs no
* more than the bytes it prints. To decide between `name: {}` and an indented
* block, the caller renders the body, then rolls back with mark()/reset() if it
* turned out empty — an O(1) decision that needs no separate child buffer.
*/
export declare class Writer {
private readonly chunks;
private depth;
/**
* Write a scalar field: `<indent>name: value` followed by a newline.
*/
scalar(name: string, value: string): void;
/**
* Write an empty message field: `<indent>name: {}` followed by a newline.
*/
emptyMessage(name: string): void;
/**
* Open a message field: `<indent>name: {` followed by a newline, then indent
* the body. Close it with end().
*/
openMessage(name: string): void;
/**
* Close a message opened with openMessage(): outdent and write `<indent>}`
* followed by a newline.
*/
end(): void;
/**
* Capture the current output position so it can be rolled back with reset().
*/
mark(): Mark;
/**
* Roll back to a position captured with mark().
*/
reset(mark: Mark): void;
/**
* The number of lines written since the given mark.
*/
writesSince(mark: Mark): number;
toString(): string;
private indent;
}
/**
* Quote and escape a string field value as a double-quoted text format literal.
*
* Uses the single escaping decision in escapeCodePoint, so string fields, bytes
* fields, and unknown length-delimited rendering can never drift apart. Valid
* non-ASCII passes through as raw UTF-8; surrogates are never escaped.
*/
export declare function quoteString(value: string): string;
/**
* Quote and escape a bytes field value as a double-quoted text format literal.
*
* Valid UTF-8 runs are emitted with escapeCodePoint (so they read identically
* to a string field); any byte that is not part of a valid UTF-8 sequence is
* emitted as `\xHH`, keeping the output plain ASCII that round-trips exactly.
*/
export declare function quoteBytes(value: Uint8Array): string;
export {};
"use strict";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.Writer = void 0;
exports.quoteString = quoteString;
exports.quoteBytes = quoteBytes;
const indentUnit = " ";
/**
* A writer for the protobuf text format.
*
* The Writer owns layout: indentation, line breaks, and braces. Its output
* matches the default formatting of txtpbfmt and the multi-line output of
* protobuf-go: two-space indentation, `name: value` with a single space after
* the colon, submessages as `name: {` with the body indented and `}` aligned
* under the field name, and a trailing newline. It never inserts the randomized
* extra spaces that protobuf-go adds to discourage parsing its output as
* canonical.
*
* Output accumulates into a single buffer of lines, so a deep tree costs no
* more than the bytes it prints. To decide between `name: {}` and an indented
* block, the caller renders the body, then rolls back with mark()/reset() if it
* turned out empty — an O(1) decision that needs no separate child buffer.
*/
class Writer {
constructor() {
this.chunks = [];
this.depth = 0;
}
/**
* Write a scalar field: `<indent>name: value` followed by a newline.
*/
scalar(name, value) {
this.chunks.push(this.indent() + name + ": " + value + "\n");
}
/**
* Write an empty message field: `<indent>name: {}` followed by a newline.
*/
emptyMessage(name) {
this.chunks.push(this.indent() + name + ": {}\n");
}
/**
* Open a message field: `<indent>name: {` followed by a newline, then indent
* the body. Close it with end().
*/
openMessage(name) {
this.chunks.push(this.indent() + name + ": {\n");
this.depth++;
}
/**
* Close a message opened with openMessage(): outdent and write `<indent>}`
* followed by a newline.
*/
end() {
this.depth--;
this.chunks.push(this.indent() + "}\n");
}
/**
* Capture the current output position so it can be rolled back with reset().
*/
mark() {
return { size: this.chunks.length, depth: this.depth };
}
/**
* Roll back to a position captured with mark().
*/
reset(mark) {
this.chunks.length = mark.size;
this.depth = mark.depth;
}
/**
* The number of lines written since the given mark.
*/
writesSince(mark) {
return this.chunks.length - mark.size;
}
toString() {
return this.chunks.join("");
}
indent() {
return indentUnit.repeat(this.depth);
}
}
exports.Writer = Writer;
/**
* Quote and escape a string field value as a double-quoted text format literal.
*
* Uses the single escaping decision in escapeCodePoint, so string fields, bytes
* fields, and unknown length-delimited rendering can never drift apart. Valid
* non-ASCII passes through as raw UTF-8; surrogates are never escaped.
*/
function quoteString(value) {
var _a;
let out = '"';
for (const ch of value) {
out += (_a = escapeCodePoint(ch.codePointAt(0))) !== null && _a !== void 0 ? _a : ch;
}
return out + '"';
}
/**
* Quote and escape a bytes field value as a double-quoted text format literal.
*
* Valid UTF-8 runs are emitted with escapeCodePoint (so they read identically
* to a string field); any byte that is not part of a valid UTF-8 sequence is
* emitted as `\xHH`, keeping the output plain ASCII that round-trips exactly.
*/
function quoteBytes(value) {
var _a;
let out = '"';
for (let i = 0; i < value.length;) {
const rune = decodeUtf8(value, i);
if (rune === undefined) {
out += "\\x" + hex2(value[i]);
i++;
continue;
}
out += (_a = escapeCodePoint(rune.code)) !== null && _a !== void 0 ? _a : String.fromCodePoint(rune.code);
i += rune.size;
}
return out + '"';
}
/**
* The single source of truth for escaping a code point in a text format string
* literal. Returns the escape sequence, or undefined when the code point may be
* emitted raw.
*
* Escapes the conventional sequences, all C0 controls and DEL as `\xHH`, and
* the C1 controls (U+0080–U+009F) as `\u00HH`. Surrogates and everything else
* pass through raw.
*/
function escapeCodePoint(code) {
switch (code) {
case 0x5c:
return "\\\\";
case 0x22:
return '\\"';
case 0x0a:
return "\\n";
case 0x0d:
return "\\r";
case 0x09:
return "\\t";
}
if (code < 0x20 || code === 0x7f) {
return "\\x" + hex2(code);
}
if (code >= 0x80 && code <= 0x9f) {
return "\\u" + code.toString(16).padStart(4, "0");
}
return undefined;
}
/**
* Decode the UTF-8 sequence starting at `offset`, returning the code point and
* its byte length, or undefined if the bytes there are not valid UTF-8. We
* decode manually (rather than via TextDecoder) so an invalid byte can be
* pinpointed and escaped individually.
*/
function decodeUtf8(bytes, offset) {
const b0 = bytes[offset];
if (b0 < 0x80) {
return { code: b0, size: 1 };
}
if (b0 < 0xc0) {
return undefined;
}
if (b0 < 0xe0) {
const b1 = bytes[offset + 1];
if (!isContinuation(b1)) {
return undefined;
}
const code = ((b0 & 0x1f) << 6) | (b1 & 0x3f);
return code < 0x80 ? undefined : { code, size: 2 };
}
if (b0 < 0xf0) {
const b1 = bytes[offset + 1];
const b2 = bytes[offset + 2];
if (!isContinuation(b1) || !isContinuation(b2)) {
return undefined;
}
const code = ((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f);
if (code < 0x800 || (code >= 0xd800 && code <= 0xdfff)) {
return undefined;
}
return { code, size: 3 };
}
if (b0 < 0xf8) {
const b1 = bytes[offset + 1];
const b2 = bytes[offset + 2];
const b3 = bytes[offset + 3];
if (!isContinuation(b1) || !isContinuation(b2) || !isContinuation(b3)) {
return undefined;
}
const code = ((b0 & 0x07) << 18) |
((b1 & 0x3f) << 12) |
((b2 & 0x3f) << 6) |
(b3 & 0x3f);
if (code < 0x10000 || code > 0x10ffff) {
return undefined;
}
return { code, size: 4 };
}
return undefined;
}
function isContinuation(byte) {
return byte !== undefined && (byte & 0xc0) === 0x80;
}
function hex2(value) {
return value.toString(16).padStart(2, "0");
}
import type { DescEnum } from "./descriptors.js";
import type { UnknownEnum } from "./types.js";
/**
* Open enums can contain numeric values that are not in the set of values
* defined by the enum.
*
* This function returns true for those values, and narrows the type to
* `UnknownEnum`.
*/
export declare function isUnknownEnum(desc: DescEnum, value: number): value is UnknownEnum;
"use strict";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.isUnknownEnum = isUnknownEnum;
/**
* Open enums can contain numeric values that are not in the set of values
* defined by the enum.
*
* This function returns true for those values, and narrows the type to
* `UnknownEnum`.
*/
function isUnknownEnum(desc, value) {
return desc.value[value] === undefined;
}
import { type DescMessage } from "../descriptors.js";
import type { Registry } from "../registry.js";
import type { MessageShape } from "../types.js";
/**
* Options for parsing the protobuf text format.
*/
export interface TextReadOptions {
/**
* The registry to resolve `google.protobuf.Any` and extensions. Parsing an
* Any in its expanded form, or an extension field, requires it.
*/
registry?: Registry | undefined;
/**
* The maximum depth of nested messages to parse. A message nesting deeper
* than this fails with an error instead of exhausting the call stack.
* Defaults to 100.
*/
recursionLimit: number;
}
/**
* Parse a message from the protobuf text format.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export declare function fromText<Desc extends DescMessage>(schema: Desc, text: string, options?: Partial<TextReadOptions>): MessageShape<Desc>;
/**
* Parse a message from the protobuf text format, merging into the target.
*
* Repeated fields are appended, singular fields are overwritten (last wins),
* message fields are merged, and map entries are added (overwriting an existing
* key).
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export declare function mergeFromText<Desc extends DescMessage>(schema: Desc, target: MessageShape<Desc>, text: string, options?: Partial<TextReadOptions>): MessageShape<Desc>;
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ScalarType, } from "../descriptors.js";
import { protoInt64 } from "../proto-int64.js";
import { reflect } from "../reflect/reflect.js";
import { isFieldError } from "../reflect/error.js";
import { scalarZeroValue } from "../reflect/scalar.js";
import { toBinary } from "../to-binary.js";
import { createExtensionContainer, getExtension, hasExtension, setExtension, } from "../extensions.js";
import { getTextEncoding } from "../wire/text-encoding.js";
import { Reader } from "./reader.js";
import { isGroupLike } from "./is-group-like.js";
function makeReadContext(reader, options) {
return Object.assign(Object.assign({ recursionLimit: 100 }, options), { reader, depth: 0 });
}
/**
* Parse a message from the protobuf text format.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export function fromText(schema, text, options) {
const msg = reflect(schema);
parseText(msg, text, options);
return msg.message;
}
/**
* Parse a message from the protobuf text format, merging into the target.
*
* Repeated fields are appended, singular fields are overwritten (last wins),
* message fields are merged, and map entries are added (overwriting an existing
* key).
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export function mergeFromText(schema, target, text, options) {
parseText(reflect(schema, target), text, options);
return target;
}
function parseText(msg, text, options) {
if (!protoInt64.supported) {
throw new Error("the protobuf text format requires BigInt, which is unavailable in this environment");
}
const ctx = makeReadContext(new Reader(text), options);
try {
readMessageBody(msg, ctx, "eof");
}
catch (e) {
if (isFieldError(e)) {
throw new Error(`cannot decode ${e.field()} from text format: ${e.message}`,
// @ts-expect-error we use the ES2022 error CTOR option "cause" for better stack traces
{ cause: e });
}
throw e;
}
}
/**
* Read a message body (its fields until `close`), guarding nesting depth. The
* top-level message and every nested "{...}"/"<...>" block go through here, so
* the recursion limit counts the root too, matching fromJson and fromBinary.
*/
function readMessageBody(msg, ctx, close) {
if (++ctx.depth > ctx.recursionLimit) {
throw new Error(`recursion limit of ${ctx.recursionLimit} reached decoding ${msg.desc}`);
}
readFields(msg, ctx, close);
ctx.depth--;
}
/**
* Read the fields of a message until `close` (the matching close token, or
* "eof" for the top-level message).
*/
function readFields(msg, ctx, close) {
const seen = { fields: new Set(), oneofs: new Set() };
for (;;) {
const tok = ctx.reader.peek();
if (tok.type === "eof") {
if (close !== "eof") {
throw new Error(`unexpected end of input, expected "${close}"`);
}
return;
}
if (close !== "eof" && tok.type === close) {
ctx.reader.next();
return;
}
readField(msg, ctx, seen);
// A single optional "," or ";" may follow a field. Because the next
// iteration treats a separator as a field name and rejects it, a doubled
// separator is an error, while a single trailing one is allowed.
consumeSeparator(ctx);
}
}
function readField(msg, ctx, seen) {
var _a;
const nameTok = ctx.reader.next();
if (nameTok.type === "identifier") {
const field = fieldByTextName(msg.desc, nameTok.value);
if (field !== undefined) {
checkSeen(field, seen);
readFieldValue(msg, field, ctx);
return;
}
// Reserved field names are silently skipped; any other unknown name is an
// error. This matches protobuf-go.
if (msg.desc.proto.reservedName.includes(nameTok.value)) {
skipFieldValue(ctx);
return;
}
throw new Error(`unknown field "${nameTok.value}" for ${msg.desc}`);
}
if (nameTok.type === "[") {
const name = ctx.reader.readTypeName();
// Inside google.protobuf.Any, a bracketed name is always a type URL; in any
// other message it is an extension name.
if (msg.desc.typeName === "google.protobuf.Any") {
readExpandedAny(msg, ctx, name, seen);
return;
}
const ext = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getExtension(name);
if (ext !== undefined && ext.extendee.typeName === msg.desc.typeName) {
checkSeen(ext, seen);
readExtensionField(msg, ext, ctx);
return;
}
throw new Error(`unknown extension "[${name}]" for ${msg.desc}`);
}
if (nameTok.type === "int") {
// Like protobuf-go, a field cannot be addressed by number, so the numbered
// output of printUnknownFields cannot be read back.
throw new Error(`cannot specify field by number: ${nameTok.text}`);
}
throw new Error(`expected a field name, got ${describe(nameTok)}`);
}
// Rejects a repeated occurrence of a singular field, or a second member of the
// same oneof. Repeated and map fields may appear any number of times.
function checkSeen(field, seen) {
if (field.fieldKind === "list" || field.fieldKind === "map") {
return;
}
if (field.oneof !== undefined) {
if (seen.oneofs.has(field.oneof)) {
throw new Error(`oneof "${field.oneof.name}" is already set`);
}
seen.oneofs.add(field.oneof);
}
if (seen.fields.has(field.number)) {
const what = field.kind === "extension"
? `extension "[${field.typeName}]"`
: `field "${field.name}"`;
throw new Error(`non-repeated ${what} is repeated`);
}
seen.fields.add(field.number);
}
function readFieldValue(target, field, ctx) {
// The ":" separator is optional before a message, group, or map value, but
// required for scalars, enums, and lists of them.
const hasColon = consumeColon(ctx);
if (!colonOptional(field) && !hasColon) {
throw new Error(`expected ":" before value of field "${field.name}"`);
}
switch (field.fieldKind) {
case "scalar":
target.set(field, readScalarValue(field, field.scalar, ctx));
break;
case "enum":
target.set(field, readEnumValue(field.enum, ctx));
break;
case "message": {
const sub = target.isSet(field)
? target.get(field)
: reflect(field.message);
readMessageValue(sub, ctx);
target.set(field, sub);
break;
}
case "list":
readListField(field, target.get(field), ctx);
break;
case "map":
readMapField(field, target.get(field), ctx);
break;
}
}
function readExtensionField(msg, ext, ctx) {
// Extensions live in the unknown-field set. We read the new value into a
// container seeded with any existing value, so a repeated extension appends.
const existing = hasExtension(msg.message, ext)
? getExtension(msg.message, ext)
: undefined;
const [container, field, get] = createExtensionContainer(ext, existing);
readFieldValue(container, field, ctx);
setExtension(msg.message, ext, get());
}
function colonOptional(field) {
return (field.fieldKind === "message" ||
field.fieldKind === "map" ||
(field.fieldKind === "list" && field.listKind === "message"));
}
/**
* Read a "{ ... }" or "< ... >" block into the given message.
*/
function readMessageValue(msg, ctx) {
readMessageBody(msg, ctx, readMessageOpen(ctx));
}
function readMessageOpen(ctx) {
const open = ctx.reader.next();
if (open.type === "{") {
return "}";
}
if (open.type === "<") {
return ">";
}
throw new Error(`expected "{" or "<", got ${describe(open)}`);
}
/**
* Read a repeated value: either a single element, or a bracketed list
* "[ e, e, ... ]". This is the one place the list grammar lives, so list
* fields, map fields, and the reserved-skip path cannot drift in how they
* accept (and reject) separators.
*/
function readBracketedList(ctx, readElement) {
if (ctx.reader.peek().type !== "[") {
readElement();
return;
}
ctx.reader.next(); // "["
if (ctx.reader.peek().type === "]") {
ctx.reader.next();
return;
}
for (;;) {
readElement();
const sep = ctx.reader.next();
if (sep.type === "]") {
return;
}
if (sep.type !== ",") {
throw new Error(`expected "," or "]" in list, got ${describe(sep)}`);
}
}
}
function readListField(field, list, ctx) {
readBracketedList(ctx, () => list.add(readListItem(field, ctx)));
}
function readListItem(field, ctx) {
switch (field.listKind) {
case "scalar":
return readScalarValue(field, field.scalar, ctx);
case "enum":
return readEnumValue(field.enum, ctx);
case "message": {
const sub = reflect(field.message);
readMessageValue(sub, ctx);
return sub;
}
}
}
function readMapField(field, map, ctx) {
readBracketedList(ctx, () => readMapEntry(field, map, ctx));
}
/**
* Read a map entry: a "{ key: ... value: ... }" block. A missing key or value
* defaults to the zero value, like protobuf-go. A duplicate "key" or "value"
* within one entry is an error; duplicate keys across separate entries are
* legal, with the last entry winning (handled by the caller's map.set).
*/
function readMapEntry(field, map, ctx) {
if (++ctx.depth > ctx.recursionLimit) {
throw new Error(`recursion limit of ${ctx.recursionLimit} reached decoding a map entry`);
}
const close = readMessageOpen(ctx);
let key = scalarZeroValue(field.mapKey, false);
let value = mapValueZero(field);
let keySeen = false;
let valueSeen = false;
for (;;) {
const tok = ctx.reader.peek();
if (tok.type === close) {
ctx.reader.next();
break;
}
if (tok.type === "eof") {
throw new Error(`unexpected end of input, expected "${close}"`);
}
const nameTok = ctx.reader.next();
if (nameTok.type !== "identifier") {
throw new Error(`expected "key" or "value", got ${describe(nameTok)}`);
}
if (nameTok.value === "key") {
if (keySeen) {
throw new Error('map entry "key" is already set');
}
keySeen = true;
requireColon(ctx);
key = readScalarValue(field, field.mapKey, ctx);
}
else if (nameTok.value === "value") {
if (valueSeen) {
throw new Error('map entry "value" is already set');
}
valueSeen = true;
value = readMapValue(field, ctx);
}
else {
throw new Error(`unknown field "${nameTok.value}" in map entry`);
}
consumeSeparator(ctx);
}
ctx.depth--;
map.set(key, value);
}
function readMapValue(field, ctx) {
switch (field.mapKind) {
case "scalar":
requireColon(ctx);
return readScalarValue(field, field.scalar, ctx);
case "enum":
requireColon(ctx);
return readEnumValue(field.enum, ctx);
case "message": {
consumeColon(ctx);
const sub = reflect(field.message);
readMessageValue(sub, ctx);
return sub;
}
}
}
function mapValueZero(field) {
switch (field.mapKind) {
case "scalar":
return scalarZeroValue(field.scalar, false);
case "enum":
return field.enum.values[0].number;
case "message":
return reflect(field.message);
}
}
/**
* Read `google.protobuf.Any` in its expanded form `[type.url]: { ... }`.
*
* The expanded form is mutually exclusive with the raw `type_url` (field 1) and
* `value` (field 2) fields, and may appear only once. We enforce that through
* the same seen-set the duplicate-field check uses: the expansion is rejected
* if either field is already set, and it marks both as set so a following
* `type_url` or `value` is rejected too.
*/
function readExpandedAny(msg, ctx, typeUrl, seen) {
var _a;
if (seen.fields.has(1) || seen.fields.has(2)) {
throw new Error("google.protobuf.Any cannot mix the expanded form with type_url/value");
}
const slash = typeUrl.lastIndexOf("/");
const typeName = slash >= 0 ? typeUrl.substring(slash + 1) : typeUrl;
const desc = (_a = ctx.registry) === null || _a === void 0 ? void 0 : _a.getMessage(typeName);
if (desc === undefined) {
throw new Error(`unable to resolve "${typeUrl}" for google.protobuf.Any`);
}
consumeColon(ctx);
const unpacked = reflect(desc);
readMessageValue(unpacked, ctx);
const any = msg.message;
// Preserve the exact type URL, including any custom domain prefix.
any.typeUrl = typeUrl;
any.value = toBinary(desc, unpacked.message);
seen.fields.add(1);
seen.fields.add(2);
}
// Consume an optional leading "-" sign and report whether one was present.
// This sees a sign token only before a number (the scanner glues a sign onto an
// identifier as in "-inf"), and whitespace and comments between the sign and the
// number are insignificant, so "- 42" means -42, matching protobuf-go.
function consumeSign(ctx) {
if (ctx.reader.peek().type === "-") {
ctx.reader.next();
return true;
}
return false;
}
function readScalarValue(field, type, ctx) {
const negative = consumeSign(ctx);
const tok = ctx.reader.next();
switch (type) {
case ScalarType.STRING:
case ScalarType.BYTES: {
if (negative) {
throw new Error("a string value cannot have a sign");
}
if (tok.type !== "string") {
throw new Error(`expected a string, got ${describe(tok)}`);
}
const bytes = concatStrings(tok, ctx);
if (type === ScalarType.BYTES) {
return bytes;
}
try {
return getTextEncoding().decodeUtf8(bytes, field.utf8Validation);
}
catch (_a) {
throw new Error("invalid UTF-8 in string");
}
}
case ScalarType.BOOL:
return readBoolValue(tok, negative);
case ScalarType.FLOAT:
// Round to 32-bit precision: an out-of-range value becomes ±inf, which is
// what the text format requires for float overflow.
return Math.fround(readFloatValue(tok, negative));
case ScalarType.DOUBLE:
return readFloatValue(tok, negative);
case ScalarType.UINT32:
case ScalarType.FIXED32:
return Number(readUnsignedInt(tok, negative));
case ScalarType.UINT64:
case ScalarType.FIXED64:
return readUnsignedInt(tok, negative);
case ScalarType.INT64:
case ScalarType.SINT64:
case ScalarType.SFIXED64:
return readSignedInt(tok, negative);
default:
// INT32, SINT32, SFIXED32: the reflect layer range-checks the number.
return Number(readSignedInt(tok, negative));
}
}
function readEnumValue(descEnum, ctx) {
const negative = consumeSign(ctx);
const tok = ctx.reader.next();
if (tok.type === "identifier") {
if (negative) {
throw new Error(`invalid enum value "-${tok.value}" for ${descEnum}`);
}
const value = descEnum.values.find((v) => v.name === tok.value);
if (value === undefined) {
throw new Error(`unknown enum value "${tok.value}" for ${descEnum}`);
}
return value.number;
}
if (tok.type === "int") {
// The reflect layer validates the number: any int32 for open enums, a known
// value for closed (proto2) enums.
return Number(readSignedInt(tok, negative));
}
throw new Error(`expected an enum value for ${descEnum}, got ${describe(tok)}`);
}
function readBoolValue(tok, negative) {
if (!negative) {
if (tok.type === "identifier") {
switch (tok.value) {
case "true":
case "True":
case "t":
return true;
case "false":
case "False":
case "f":
return false;
}
}
if (tok.type === "int") {
const value = intTokenToBigInt(tok);
if (value === BigInt(0)) {
return false;
}
if (value === BigInt(1)) {
return true;
}
}
}
throw new Error(`expected a bool, got ${negative ? "-" : ""}${describe(tok)}`);
}
function readFloatValue(tok, negative) {
if (tok.type === "identifier") {
// A separate "-" token (negative) before a float literal is invalid; the
// only signed literals are "-inf"/"-infinity", which the scanner glues into
// one identifier token. "-nan" is not a literal, so it falls through to the
// error below. This matches protobuf-go's identifier-path sign handling.
if (negative) {
throw new Error(`invalid float value "-${tok.value}"`);
}
switch (tok.value.toLowerCase()) {
case "inf":
case "infinity":
return Number.POSITIVE_INFINITY;
case "-inf":
case "-infinity":
return Number.NEGATIVE_INFINITY;
case "nan":
return Number.NaN;
}
throw new Error(`invalid float value "${tok.value}"`);
}
if (tok.type === "float") {
const n = Number(tok.text);
return negative ? -n : n;
}
if (tok.type === "int") {
// Octal and hexadecimal literals are not valid for float and double fields.
if (tok.base !== 10) {
throw new Error("octal and hexadecimal are not valid for a float field");
}
const n = Number(tok.text);
return negative ? -n : n;
}
throw new Error(`expected a float, got ${describe(tok)}`);
}
function readSignedInt(tok, negative) {
if (tok.type !== "int") {
throw new Error(`expected an integer, got ${describe(tok)}`);
}
const value = intTokenToBigInt(tok);
return negative ? -value : value;
}
function readUnsignedInt(tok, negative) {
// Reject any sign for an unsigned field, including "-0": the reflect layer
// would silently accept it as 0.
if (negative) {
throw new Error("an unsigned field does not accept a negative value");
}
return readSignedInt(tok, false);
}
function intTokenToBigInt(tok) {
// Octal text keeps its leading "0" (e.g. "0755"), which BigInt would read as
// decimal, so it needs the "0o" prefix. Hex ("0x...") and decimal text are
// accepted by BigInt as-is.
return tok.base === 8
? BigInt("0o" + tok.text.substring(1))
: BigInt(tok.text);
}
// Concatenate adjacent string literals into a single byte string.
function concatStrings(first, ctx) {
if (ctx.reader.peek().type !== "string") {
return first.value;
}
const parts = [first.value];
let length = first.value.length;
while (ctx.reader.peek().type === "string") {
const tok = ctx.reader.next();
parts.push(tok.value);
length += tok.value.length;
}
const bytes = new Uint8Array(length);
let offset = 0;
for (const part of parts) {
bytes.set(part, offset);
offset += part.length;
}
return bytes;
}
/**
* Skip the value of a reserved field. Like every other nested read, the message
* case is guarded by the recursion limit.
*/
function skipFieldValue(ctx) {
consumeColon(ctx);
skipValue(ctx);
}
function skipValue(ctx) {
readBracketedList(ctx, () => skipSingleValue(ctx));
}
function skipSingleValue(ctx) {
const tok = ctx.reader.peek();
if (tok.type === "{" || tok.type === "<") {
skipMessageBlock(ctx);
return;
}
// A leading sign is consumed leniently here: skipping a reserved value should
// tolerate "- 5".
consumeSign(ctx);
const value = ctx.reader.next();
if (value.type === "string") {
while (ctx.reader.peek().type === "string") {
ctx.reader.next();
}
return;
}
if (value.type !== "identifier" &&
value.type !== "int" &&
value.type !== "float") {
throw new Error(`expected a value, got ${describe(value)}`);
}
}
function skipMessageBlock(ctx) {
if (++ctx.depth > ctx.recursionLimit) {
throw new Error(`recursion limit of ${ctx.recursionLimit} reached skipping a reserved field`);
}
const close = readMessageOpen(ctx);
for (;;) {
const tok = ctx.reader.peek();
if (tok.type === close) {
ctx.reader.next();
ctx.depth--;
return;
}
if (tok.type === "eof") {
throw new Error(`unexpected end of input, expected "${close}"`);
}
const nameTok = ctx.reader.next();
if (nameTok.type === "[") {
ctx.reader.readTypeName();
}
else if (nameTok.type !== "identifier" && nameTok.type !== "int") {
throw new Error(`expected a field name, got ${describe(nameTok)}`);
}
skipFieldValue(ctx);
consumeSeparator(ctx);
}
}
// Consume an optional "," or ";" that separates fields or list/map elements.
function consumeSeparator(ctx) {
const sep = ctx.reader.peek();
if (sep.type === "," || sep.type === ";") {
ctx.reader.next();
}
}
function consumeColon(ctx) {
if (ctx.reader.peek().type === ":") {
ctx.reader.next();
return true;
}
return false;
}
function requireColon(ctx) {
if (!consumeColon(ctx)) {
throw new Error(`expected ":", got ${describe(ctx.reader.peek())}`);
}
}
const textFieldCache = new WeakMap();
/**
* Resolve a field by its text format name, mirroring protobuf-go's ByTextName:
* group-like fields are addressed by their message type name, with the
* lowercase form as an alias; JSON names are not in this table.
*/
function fieldByTextName(desc, name) {
let byText = textFieldCache.get(desc);
if (byText === undefined) {
byText = new Map();
for (const field of desc.fields) {
if (isGroupLike(field)) {
setOnce(byText, field.message.name, field);
setOnce(byText, field.message.name.toLowerCase(), field);
}
else {
setOnce(byText, field.name, field);
}
}
textFieldCache.set(desc, byText);
}
return byText.get(name);
}
function setOnce(map, key, field) {
if (!map.has(key)) {
map.set(key, field);
}
}
function describe(tok) {
switch (tok.type) {
case "identifier":
return `"${tok.value}"`;
case "int":
case "float":
return `"${tok.text}"`;
case "string":
return "a string";
case "eof":
return "end of input";
default:
return `"${tok.type}"`;
}
}
export { toText } from "./to-text.js";
export type { TextWriteOptions } from "./to-text.js";
export { fromText, mergeFromText } from "./from-text.js";
export type { TextReadOptions } from "./from-text.js";
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export { toText } from "./to-text.js";
export { fromText, mergeFromText } from "./from-text.js";
import type { DescField, DescMessage } from "../descriptors.js";
/**
* Returns true if the field is structured like a proto2 group: a delimited
* message field whose name is the lowercase of its message type name, declared
* in the same scope as that message.
*
* The text format addresses such fields by their message type name (e.g.
* `MyGroup`) rather than their field name. This is a faithful port of
* protobuf-go's isGroupLike (internal/filedesc/desc.go), so editions delimited
* fields are treated exactly like proto2 groups.
*
* Testing `field.message` first narrows the DescField union to its three
* message-bearing variants (singular, list, and map value) — all of which carry
* `delimitedEncoding` — so it is in scope below without a cast. Maps are
* excluded automatically, because their `delimitedEncoding` is always false.
*/
export declare function isGroupLike(field: DescField): field is DescField & {
message: DescMessage;
};
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Returns true if the field is structured like a proto2 group: a delimited
* message field whose name is the lowercase of its message type name, declared
* in the same scope as that message.
*
* The text format addresses such fields by their message type name (e.g.
* `MyGroup`) rather than their field name. This is a faithful port of
* protobuf-go's isGroupLike (internal/filedesc/desc.go), so editions delimited
* fields are treated exactly like proto2 groups.
*
* Testing `field.message` first narrows the DescField union to its three
* message-bearing variants (singular, list, and map value) — all of which carry
* `delimitedEncoding` — so it is in scope below without a cast. Maps are
* excluded automatically, because their `delimitedEncoding` is always false.
*/
export function isGroupLike(field) {
// Groups are always delimited-encoded message fields.
if (field.message === undefined || !field.delimitedEncoding) {
return false;
}
// Group fields are always named after the lowercase message type name.
if (field.message.name.toLowerCase() !== field.name) {
return false;
}
// Groups can only be defined in the file they are used in.
if (field.message.file !== field.parent.file) {
return false;
}
// Group messages are always defined in the same scope as the field.
return field.message.parent === field.parent;
}
/**
* A lexical token of the protobuf text format.
*
* This is a discriminated union keyed by `type`: punctuation tokens carry no
* payload, identifiers carry their text, string tokens carry their decoded
* bytes (the same bytes back both string and bytes fields, and bytes fields may
* hold sequences that are not valid UTF-8), and numbers carry their literal
* text plus enough classification for the parser to accept or reject them per
* field type.
*
* The minus sign before a number is its own token rather than part of the
* number, which keeps numeric sign handling in one place in the parser and,
* because whitespace and comments between tokens are insignificant, makes
* `- 42` mean `-42`, matching protobuf-go (decode_number.go). A minus glued to
* a letter is instead folded into a negative identifier (`-inf`/`-infinity`),
* because protobuf-go requires the sign glued for those literals — `- inf` is
* an error there, not negative infinity.
*/
export type Token = {
readonly type: Structural | "eof";
} | {
readonly type: "identifier";
readonly value: string;
} | {
readonly type: "string";
readonly value: Uint8Array;
} | {
readonly type: "int";
readonly text: string;
readonly base: 8 | 10 | 16;
} | {
readonly type: "float";
readonly text: string;
};
/**
* The structural tokens, each the literal source character it represents.
*/
type Structural = "{" | "}" | "<" | ">" | "[" | "]" | ":" | "," | ";" | "-";
/**
* A tokenizer for the protobuf text format.
*
* The parser drives it with `peek()` and `next()` (one-token lookahead) and,
* once it knows it is in a field-name position, asks for the contents of a
* bracketed name with `readTypeName()` — the `[...]` syntax for extensions and
* Any type URLs is ambiguous with the list syntax at the lexical level. The
* structure is modeled on the graphql-js lexer: a single scan position and a
* per-token reader that returns the decoded value.
*/
export declare class Reader {
private readonly input;
private readonly length;
private pos;
private lookahead;
constructor(input: string);
/**
* Return the next token without consuming it.
*/
peek(): Token;
/**
* Consume and return the next token.
*/
next(): Token;
/**
* Read the contents of a bracketed name, used for extension fields and the
* expanded form of google.protobuf.Any. The opening `[` must already have
* been consumed with `next()`. Whitespace and comments inside the brackets
* are insignificant. Returns the inner name with the brackets removed, e.g.
* "pkg.Message.field" or "type.googleapis.com/pkg.Message".
*
* The text format grammar for this is incomplete, so we follow protobuf-go's
* parseTypeName: the prefix may contain URL characters, `/` separators, and
* well-formed percent-escapes, and the type name after the last `/` must be a
* dotted identifier.
*/
readTypeName(): string;
private scan;
private skipSpace;
private scanIdentifier;
/**
* Scan a numeric literal. The sign is a separate token, so a number never
* starts with `-`. The literal must end at a delimiter, so `10f` is a float
* but `10bar`, `1.2.3`, `09`, and `0xZ` are errors.
*/
private scanNumber;
private expectDelimiter;
private scanString;
private scanEscape;
private takeWhile;
private charAt;
}
export {};
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { getTextEncoding } from "../wire/text-encoding.js";
const tokenEof = { type: "eof" };
/**
* A tokenizer for the protobuf text format.
*
* The parser drives it with `peek()` and `next()` (one-token lookahead) and,
* once it knows it is in a field-name position, asks for the contents of a
* bracketed name with `readTypeName()` — the `[...]` syntax for extensions and
* Any type URLs is ambiguous with the list syntax at the lexical level. The
* structure is modeled on the graphql-js lexer: a single scan position and a
* per-token reader that returns the decoded value.
*/
export class Reader {
constructor(input) {
this.pos = 0;
// A leading byte-order mark is insignificant; skip it like protobuf-go's
// tokenizer does.
this.input = input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
this.length = this.input.length;
}
/**
* Return the next token without consuming it.
*/
peek() {
if (this.lookahead === undefined) {
this.lookahead = this.scan();
}
return this.lookahead;
}
/**
* Consume and return the next token.
*/
next() {
const tok = this.peek();
this.lookahead = undefined;
return tok;
}
/**
* Read the contents of a bracketed name, used for extension fields and the
* expanded form of google.protobuf.Any. The opening `[` must already have
* been consumed with `next()`. Whitespace and comments inside the brackets
* are insignificant. Returns the inner name with the brackets removed, e.g.
* "pkg.Message.field" or "type.googleapis.com/pkg.Message".
*
* The text format grammar for this is incomplete, so we follow protobuf-go's
* parseTypeName: the prefix may contain URL characters, `/` separators, and
* well-formed percent-escapes, and the type name after the last `/` must be a
* dotted identifier.
*/
readTypeName() {
let name = "";
for (;;) {
this.skipSpace();
const c = this.charAt(this.pos);
if (c === undefined) {
throw new Error("unterminated [...] name");
}
if (c === "]") {
this.pos++;
break;
}
if (c === "/") {
name += "/";
this.pos++;
}
else if (c === "%") {
if (!isHexDigit(this.charAt(this.pos + 1)) ||
!isHexDigit(this.charAt(this.pos + 2))) {
throw new Error("invalid percent-escape in [...] name");
}
name += this.input.substring(this.pos, this.pos + 3);
this.pos += 3;
}
else if (isUrlChar(c)) {
name += c;
this.pos++;
}
else {
throw new Error(`unexpected ${quoteChar(c)} in [...] name`);
}
}
validateTypeName(name);
return name;
}
scan() {
this.skipSpace();
const c = this.charAt(this.pos);
if (c === undefined) {
return tokenEof;
}
switch (c) {
case "{":
case "}":
case "<":
case ">":
case "[":
case "]":
case ":":
case ",":
case ";":
this.pos++;
return { type: c };
case "-":
// A "-" glued to a letter begins a negative identifier (-inf or
// -infinity); otherwise it is a sign token. A number may have whitespace
// between the sign and the digits, so the sign is a separate token the
// parser reassembles; a float literal may not, matching protobuf-go,
// where inf/infinity parse through the identifier path with the sign
// glued (so "- inf" is an error but "- 42" is -42).
if (isLetter(this.charAt(this.pos + 1))) {
return this.scanIdentifier();
}
this.pos++;
return { type: "-" };
case '"':
case "'":
return this.scanString(c);
}
if (isDigit(c)) {
return this.scanNumber();
}
if (c === "." && isDigit(this.charAt(this.pos + 1))) {
return this.scanNumber();
}
if (isLetter(c)) {
return this.scanIdentifier();
}
throw new Error(`unexpected ${quoteChar(c)}`);
}
skipSpace() {
for (;;) {
const c = this.input[this.pos];
if (c === " " ||
c === "\t" ||
c === "\n" ||
c === "\r" ||
c === "\v" ||
c === "\f") {
this.pos++;
}
else if (c === "#") {
// A comment runs to the end of the line.
this.pos++;
while (this.pos < this.length && this.input[this.pos] !== "\n") {
this.pos++;
}
}
else {
return;
}
}
}
scanIdentifier() {
const start = this.pos;
if (this.charAt(this.pos) === "-") {
this.pos++; // a glued negative identifier such as -inf
}
this.pos++; // the first letter (the caller guarantees one is present)
while (isLetterOrDigit(this.charAt(this.pos))) {
this.pos++;
}
return { type: "identifier", value: this.input.substring(start, this.pos) };
}
/**
* Scan a numeric literal. The sign is a separate token, so a number never
* starts with `-`. The literal must end at a delimiter, so `10f` is a float
* but `10bar`, `1.2.3`, `09`, and `0xZ` are errors.
*/
scanNumber() {
var _a, _b, _c, _d;
const start = this.pos;
if (this.input[this.pos] === "0" &&
/[xX]/.test((_a = this.charAt(this.pos + 1)) !== null && _a !== void 0 ? _a : "")) {
// Hexadecimal: `0x` followed by one or more hex digits.
this.pos += 2;
const digits = this.pos;
while (isHexDigit(this.charAt(this.pos))) {
this.pos++;
}
if (this.pos === digits) {
throw new Error("invalid hexadecimal literal");
}
this.expectDelimiter();
return {
type: "int",
text: this.input.substring(start, this.pos),
base: 16,
};
}
if (this.input[this.pos] === "0" &&
isOctalDigit(this.charAt(this.pos + 1))) {
// Octal: a leading `0` followed by octal digits. A subsequent non-octal
// digit (as in `078`) ends the run, and the delimiter check rejects it.
this.pos++;
while (isOctalDigit(this.charAt(this.pos))) {
this.pos++;
}
this.expectDelimiter();
return {
type: "int",
text: this.input.substring(start, this.pos),
base: 8,
};
}
// A decimal integer or a floating point literal. A leading "0" stands
// alone (octal and hex were handled above), so the delimiter check below
// rejects a following digit — `08` and `09` are malformed, not decimal.
let isFloat = false;
if (this.charAt(this.pos) === "0") {
this.pos++;
}
else {
while (isDigit(this.charAt(this.pos))) {
this.pos++;
}
}
if (this.charAt(this.pos) === ".") {
isFloat = true;
this.pos++;
while (isDigit(this.charAt(this.pos))) {
this.pos++;
}
}
if (/[eE]/.test((_b = this.charAt(this.pos)) !== null && _b !== void 0 ? _b : "")) {
isFloat = true;
this.pos++;
if (/[+-]/.test((_c = this.charAt(this.pos)) !== null && _c !== void 0 ? _c : "")) {
this.pos++;
}
const digits = this.pos;
while (isDigit(this.charAt(this.pos))) {
this.pos++;
}
if (this.pos === digits) {
throw new Error("invalid exponent");
}
}
// A trailing `f`/`F` marks a float and is not part of the value passed to
// Number(); capture the end before consuming it so it stays out of the text.
const end = this.pos;
if (/[fF]/.test((_d = this.charAt(this.pos)) !== null && _d !== void 0 ? _d : "")) {
isFloat = true;
this.pos++;
}
this.expectDelimiter();
const text = this.input.substring(start, end);
return isFloat ? { type: "float", text } : { type: "int", text, base: 10 };
}
// A number must be terminated by a delimiter — any character that cannot
// continue a name or number. This rejects `09`, `0xZ`, `1.2.3`, and `5bar`.
expectDelimiter() {
const c = this.charAt(this.pos);
if (c !== undefined &&
(isLetterOrDigit(c) || c === "-" || c === "+" || c === ".")) {
throw new Error("invalid number");
}
}
scanString(quote) {
this.pos++; // opening quote
const bytes = [];
// Literal characters are accumulated as a run and encoded as UTF-8 in one
// batch when the run ends (at an escape or the closing quote). Escapes
// contribute their bytes directly.
let runStart = this.pos;
for (;;) {
const c = this.charAt(this.pos);
if (c === undefined) {
throw new Error("unterminated string");
}
if (c === quote) {
pushUtf8(bytes, this.input.substring(runStart, this.pos));
this.pos++; // closing quote
return { type: "string", value: new Uint8Array(bytes) };
}
if (c === "\\") {
pushUtf8(bytes, this.input.substring(runStart, this.pos));
this.pos++; // backslash
this.scanEscape(bytes);
runStart = this.pos;
continue;
}
// A raw newline or NUL is not allowed in a string, matching protobuf-go.
if (c === "\n" || c === "\0") {
throw new Error(`invalid ${quoteChar(c)} in string`);
}
this.pos++;
}
}
scanEscape(bytes) {
const c = this.charAt(this.pos);
if (c === undefined) {
throw new Error("unterminated escape sequence");
}
switch (c) {
case '"':
case "'":
case "\\":
case "?":
bytes.push(c.charCodeAt(0));
this.pos++;
return;
case "a":
bytes.push(0x07);
this.pos++;
return;
case "b":
bytes.push(0x08);
this.pos++;
return;
case "f":
bytes.push(0x0c);
this.pos++;
return;
case "n":
bytes.push(0x0a);
this.pos++;
return;
case "r":
bytes.push(0x0d);
this.pos++;
return;
case "t":
bytes.push(0x09);
this.pos++;
return;
case "v":
bytes.push(0x0b);
this.pos++;
return;
case "x": {
this.pos++;
const hex = this.takeWhile(isHexDigit, 2);
if (hex.length === 0) {
throw new Error("invalid hex escape \\x");
}
bytes.push(parseInt(hex, 16));
return;
}
case "u":
case "U": {
this.pos++;
const width = c === "u" ? 4 : 8;
const hex = this.takeWhile(isHexDigit, width);
if (hex.length !== width) {
throw new Error(`invalid unicode escape \\${c}`);
}
const code = parseInt(hex, 16);
// Reject surrogate code points and values beyond U+10FFFF. We
// deliberately do NOT combine an adjacent `\u` low surrogate into a
// pair the way protobuf-go does: the conformance suite (the
// StringLiteral*Surrogate* cases) requires every surrogate escape, lone
// or paired, to be a parse error. Do not "fix" this toward Go.
if (code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
throw new Error(`invalid unicode escape \\${c}${hex}`);
}
pushUtf8(bytes, String.fromCodePoint(code));
return;
}
default:
if (isOctalDigit(c)) {
const oct = this.takeWhile(isOctalDigit, 3);
const value = parseInt(oct, 8);
if (value > 0xff) {
throw new Error(`octal escape \\${oct} out of range`);
}
bytes.push(value);
return;
}
throw new Error(`invalid escape \\${c}`);
}
}
// Consume up to `max` consecutive characters matching `pred` and return them.
takeWhile(pred, max) {
const start = this.pos;
while (this.pos < start + max && pred(this.charAt(this.pos))) {
this.pos++;
}
return this.input.substring(start, this.pos);
}
charAt(index) {
return index < this.length ? this.input[index] : undefined;
}
}
/**
* Validate a type name (and URL prefix) from a bracketed name, mirroring
* protobuf-go's parseTypeName. The type name is everything after the last `/`;
* the prefix before it is a URL that may carry extra characters and
* percent-escapes, but must not begin with `/`.
*/
function validateTypeName(name) {
const lastSlash = name.lastIndexOf("/");
if (lastSlash >= 0 && name[0] === "/") {
throw new Error("invalid type name: empty URL host");
}
const typeName = name.substring(lastSlash + 1);
if (typeName.length === 0) {
throw new Error("invalid type name: empty");
}
for (const part of typeName.split(".")) {
if (part.length === 0) {
throw new Error("invalid type name: empty component");
}
}
for (const c of typeName) {
if (!(isLetterOrDigit(c) || c === "." || c === "-")) {
throw new Error(`unexpected ${quoteChar(c)} in type name`);
}
}
}
function isDigit(c) {
return c !== undefined && c >= "0" && c <= "9";
}
function isOctalDigit(c) {
return c !== undefined && c >= "0" && c <= "7";
}
function isHexDigit(c) {
return (c !== undefined &&
((c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F")));
}
function isLetter(c) {
return (c !== undefined &&
((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_"));
}
function isLetterOrDigit(c) {
return isLetter(c) || isDigit(c);
}
/**
* A character permitted in the URL prefix of an Any type name, matching
* protobuf-go's isUrlChar plus the type-name characters.
*/
function isUrlChar(c) {
return (isLetterOrDigit(c) ||
c === "-" ||
c === "." ||
c === "~" ||
c === "!" ||
c === "$" ||
c === "&" ||
c === "(" ||
c === ")" ||
c === "*" ||
c === "+" ||
c === "," ||
c === ";" ||
c === "=");
}
function quoteChar(c) {
var _a;
const code = (_a = c.codePointAt(0)) !== null && _a !== void 0 ? _a : 0;
return code >= 0x20 && code <= 0x7e
? `"${c}"`
: `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
}
// Append the UTF-8 encoding of `text` to `out`, using the same Text Encoding
// API as the rest of the library. Unpaired surrogates become U+FFFD (the
// standard TextEncoder behavior), matching binary serialization.
function pushUtf8(out, text) {
if (text.length === 0) {
return;
}
for (const byte of getTextEncoding().encodeUtf8(text)) {
out.push(byte);
}
}
import { type DescMessage } from "../descriptors.js";
import type { Registry } from "../registry.js";
import type { MessageShape } from "../types.js";
/**
* Options for serializing to the protobuf text format.
*
* The text format represents 64-bit integral types with BigInt and has no
* string fall-back: toText throws immediately when BigInt is unavailable.
*/
export interface TextWriteOptions {
/**
* Print unknown fields?
*
* Disabled by default. This is a debugging aid only: unknown fields are
* printed by field number, and fromText rejects fields named by number, so
* output that includes them cannot be parsed back.
*/
printUnknownFields: boolean;
/**
* The registry to resolve `google.protobuf.Any` and extensions. Without it,
* an Any is written as its raw `type_url`/`value` fields and extensions are
* omitted.
*/
registry?: Registry | undefined;
}
/**
* Serialize a message to the protobuf text format.
*
* The output matches the default formatting of txtpbfmt: two-space indentation,
* one field per line, and a trailing newline.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export declare function toText<Desc extends DescMessage>(schema: Desc, message: MessageShape<Desc>, options?: Partial<TextWriteOptions>): string;
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ScalarType, } from "../descriptors.js";
import { protoInt64 } from "../proto-int64.js";
import { reflect } from "../reflect/reflect.js";
import { createExtensionContainer, getExtension } from "../extensions.js";
import { BinaryReader, WireType } from "../wire/index.js";
import { anyUnpack } from "../wkt/index.js";
import { isGroupLike } from "./is-group-like.js";
import { quoteBytes, quoteString, Writer } from "./writer.js";
const textWriteDefaults = {
printUnknownFields: false,
};
function makeWriteOptions(options) {
return options ? Object.assign(Object.assign({}, textWriteDefaults), options) : textWriteDefaults;
}
// A bound on nested unknown-field rendering. The known-field tree is a finite,
// valid in-memory message and needs no limit, but printUnknownFields re-parses
// bytes and recurses (the length-delimited and group heuristics), so we cap
// that path as defense-in-depth.
const unknownFieldDepthLimit = 100;
/**
* Serialize a message to the protobuf text format.
*
* The output matches the default formatting of txtpbfmt: two-space indentation,
* one field per line, and a trailing newline.
*
* Requires BigInt: throws immediately if the environment does not support it.
*/
export function toText(schema, message, options) {
if (!protoInt64.supported) {
throw new Error("the protobuf text format requires BigInt, which is unavailable in this environment");
}
const writer = new Writer();
writeMessage(writer, reflect(schema, message), makeWriteOptions(options));
return writer.toString();
}
/**
* Write the body of a message: regular fields in declaration order, then
* resolvable extensions sorted by full name, then unknown fields by number
* (only when printUnknownFields is enabled). For `google.protobuf.Any`, the
* expanded form replaces all of this.
*/
function writeMessage(writer, msg, opts) {
var _a;
if (writeAny(writer, msg, opts)) {
return;
}
for (const field of msg.fields) {
// Unset fields are omitted, including unset required fields; like
// protobuf-go, we do not validate required fields when serializing.
if (msg.isSet(field)) {
writeField(writer, fieldTextName(field), field, msg, opts);
}
}
const extensionNumbers = writeExtensions(writer, msg, opts);
if (opts.printUnknownFields) {
for (const field of (_a = msg.getUnknown()) !== null && _a !== void 0 ? _a : []) {
if (!extensionNumbers.has(field.no)) {
writeUnknownField(writer, field, 0);
}
}
}
}
function writeField(writer, name, field, msg, opts) {
// Narrowing on fieldKind lets msg.get(field) return the precise reflect type
// for each case — ReflectMessage, ReflectList, ReflectMap, number, or a scalar
// value — so none of the branches need a cast.
switch (field.fieldKind) {
case "scalar":
writer.scalar(name, scalarToText(field.scalar, msg.get(field)));
break;
case "enum":
writer.scalar(name, enumToText(field.enum, msg.get(field)));
break;
case "message":
writeMessageValue(writer, name, msg.get(field), opts);
break;
case "list":
writeList(writer, name, field, msg.get(field), opts);
break;
case "map":
writeMap(writer, name, field, msg.get(field), opts);
break;
}
}
/**
* Write a message value as `name: { ... }`, or `name: {}` when it has no body.
* The body is rendered speculatively and rolled back if it turns out empty.
*/
function writeMessageValue(writer, name, msg, opts) {
const mark = writer.mark();
writer.openMessage(name);
writeMessage(writer, msg, opts);
if (writer.writesSince(mark) === 1) {
// Only the opener was written, so the message is empty.
writer.reset(mark);
writer.emptyMessage(name);
}
else {
writer.end();
}
}
function writeList(writer, name, field, list, opts) {
switch (field.listKind) {
case "scalar":
for (const item of list) {
writer.scalar(name, scalarToText(field.scalar, item));
}
break;
case "enum":
for (const item of list) {
writer.scalar(name, enumToText(field.enum, item));
}
break;
case "message":
for (const item of list) {
writeMessageValue(writer, name, item, opts);
}
break;
}
}
function writeMap(writer, name, field, map, opts) {
// Map entries are emitted in iteration (insertion) order; unlike protobuf-go,
// we deliberately do not sort them.
for (const [key, value] of map) {
writer.openMessage(name);
writer.scalar("key", scalarToText(field.mapKey, key));
switch (field.mapKind) {
case "scalar":
writer.scalar("value", scalarToText(field.scalar, value));
break;
case "enum":
writer.scalar("value", enumToText(field.enum, value));
break;
case "message":
writeMessageValue(writer, "value", value, opts);
break;
}
writer.end();
}
}
/**
* Write `google.protobuf.Any` in its expanded form `[type.url]: { ... }`.
* Returns false (so the generic path writes `type_url`/`value` instead) when
* the message is not an Any, has no type URL, or the type cannot be resolved.
*/
function writeAny(writer, msg, opts) {
if (msg.desc.typeName !== "google.protobuf.Any" ||
opts.registry === undefined) {
return false;
}
const any = msg.message;
if (any.typeUrl === "") {
return false;
}
const unpacked = anyUnpack(any, opts.registry);
if (unpacked === undefined) {
return false;
}
const desc = opts.registry.getMessage(unpacked.$typeName);
if (desc === undefined) {
return false;
}
// The bracketed name preserves the exact type URL, including a custom domain.
writeMessageValue(writer, "[" + any.typeUrl + "]", reflect(desc, unpacked), opts);
return true;
}
/**
* Write resolvable extensions, sorted by full name, and return their field
* numbers so writeMessage does not also emit them as raw unknown fields.
*/
function writeExtensions(writer, msg, opts) {
const numbers = new Set();
const unknown = msg.getUnknown();
if (opts.registry === undefined || unknown === undefined) {
return numbers;
}
const extensions = [];
for (const { no } of unknown) {
if (numbers.has(no)) {
continue;
}
const extension = opts.registry.getExtensionFor(msg.desc, no);
if (extension !== undefined) {
numbers.add(no);
extensions.push(extension);
}
}
extensions.sort((a, b) => a.typeName < b.typeName ? -1 : a.typeName > b.typeName ? 1 : 0);
for (const extension of extensions) {
const value = getExtension(msg.message, extension);
const [container, field] = createExtensionContainer(extension, value);
writeField(writer, "[" + extension.typeName + "]", field, container, opts);
}
return numbers;
}
/**
* Write an unknown field by its field number, mirroring protobuf-go: varints as
* decimal, fixed-width values as hexadecimal, length-delimited data as a nested
* message when it parses cleanly as one and a quoted byte string otherwise, and
* groups recursively.
*/
function writeUnknownField(writer, field, depth) {
const name = field.no.toString();
const reader = new BinaryReader(field.data);
switch (field.wireType) {
case WireType.Varint:
writer.scalar(name, reader.uint64().toString());
break;
case WireType.Bit32:
writer.scalar(name, "0x" + (reader.fixed32() >>> 0).toString(16).padStart(8, "0"));
break;
case WireType.Bit64:
writer.scalar(name, "0x" + BigInt(reader.fixed64()).toString(16).padStart(16, "0"));
break;
case WireType.LengthDelimited: {
const bytes = reader.bytes();
const nested = depth < unknownFieldDepthLimit ? parseUnknownMessage(bytes) : undefined;
if (nested === undefined) {
writer.scalar(name, quoteBytes(bytes));
}
else {
writeUnknownGroup(writer, name, nested, depth);
}
break;
}
case WireType.StartGroup: {
const fields = [];
while (reader.pos < reader.len) {
const [no, wireType] = reader.tag();
if (wireType === WireType.EndGroup) {
break;
}
fields.push({ no, wireType, data: reader.skip(wireType, no) });
}
writeUnknownGroup(writer, name, fields, depth);
break;
}
}
}
function writeUnknownGroup(writer, name, fields, depth) {
if (fields.length === 0) {
writer.emptyMessage(name);
return;
}
writer.openMessage(name);
for (const field of fields) {
writeUnknownField(writer, field, depth + 1);
}
writer.end();
}
/**
* Try to interpret length-delimited bytes as a nested message. Returns its
* unknown fields if the bytes parse cleanly and completely, otherwise undefined
* (in which case the data is rendered as a quoted byte string).
*/
function parseUnknownMessage(bytes) {
if (bytes.length === 0) {
return undefined;
}
const reader = new BinaryReader(bytes);
const fields = [];
try {
while (reader.pos < reader.len) {
const [no, wireType] = reader.tag();
if (no <= 0 || wireType === WireType.EndGroup) {
return undefined;
}
fields.push({ no, wireType, data: reader.skip(wireType, no) });
}
}
catch (_a) {
return undefined;
}
return reader.pos === reader.len ? fields : undefined;
}
/**
* The name a field is addressed by in the text format: a group-like (delimited)
* field uses its message type name, every other field its proto name.
*/
function fieldTextName(field) {
return isGroupLike(field) ? field.message.name : field.name;
}
function scalarToText(type, value) {
switch (type) {
case ScalarType.STRING:
return quoteString(value);
case ScalarType.BYTES:
return quoteBytes(value);
case ScalarType.BOOL:
return value === true ? "true" : "false";
case ScalarType.FLOAT:
return floatToText(value, true);
case ScalarType.DOUBLE:
return floatToText(value, false);
default:
// All integer types print as decimal with no prefix. 64-bit values are
// bigint; String() gives the decimal form for both bigint and number.
return String(value);
}
}
function enumToText(descEnum, value) {
// Emit the first-declared name for a value, so allow_alias enums match
// protobuf-go (the by-number record can resolve to a non-first alias). An
// unknown value prints as a decimal.
for (const v of descEnum.values) {
if (v.number === value) {
return v.name;
}
}
return value.toString();
}
function floatToText(value, single) {
// Round to 32-bit precision first so an overflow becomes inf (not the JS
// "Infinity") and the value is the true 32-bit value before we test it.
const n = single ? Math.fround(value) : value;
if (Number.isNaN(n)) {
return "nan";
}
if (n === Number.POSITIVE_INFINITY) {
return "inf";
}
if (n === Number.NEGATIVE_INFINITY) {
return "-inf";
}
if (Object.is(n, -0)) {
return "-0";
}
if (!single) {
// Number.prototype.toString already yields the shortest decimal that
// round-trips to the same 64-bit value.
return n.toString();
}
// For 32-bit floats, find the shortest decimal that round-trips to the same
// float32, mirroring strconv.AppendFloat(n, 'g', -1, 32) in protobuf-go.
for (let precision = 1; precision <= 9; precision++) {
const candidate = Number(n.toPrecision(precision));
if (Math.fround(candidate) === n) {
return candidate.toString();
}
}
return n.toString();
}
/**
* A position in the Writer's output, used to roll back speculative writes.
*/
interface Mark {
readonly size: number;
readonly depth: number;
}
/**
* A writer for the protobuf text format.
*
* The Writer owns layout: indentation, line breaks, and braces. Its output
* matches the default formatting of txtpbfmt and the multi-line output of
* protobuf-go: two-space indentation, `name: value` with a single space after
* the colon, submessages as `name: {` with the body indented and `}` aligned
* under the field name, and a trailing newline. It never inserts the randomized
* extra spaces that protobuf-go adds to discourage parsing its output as
* canonical.
*
* Output accumulates into a single buffer of lines, so a deep tree costs no
* more than the bytes it prints. To decide between `name: {}` and an indented
* block, the caller renders the body, then rolls back with mark()/reset() if it
* turned out empty — an O(1) decision that needs no separate child buffer.
*/
export declare class Writer {
private readonly chunks;
private depth;
/**
* Write a scalar field: `<indent>name: value` followed by a newline.
*/
scalar(name: string, value: string): void;
/**
* Write an empty message field: `<indent>name: {}` followed by a newline.
*/
emptyMessage(name: string): void;
/**
* Open a message field: `<indent>name: {` followed by a newline, then indent
* the body. Close it with end().
*/
openMessage(name: string): void;
/**
* Close a message opened with openMessage(): outdent and write `<indent>}`
* followed by a newline.
*/
end(): void;
/**
* Capture the current output position so it can be rolled back with reset().
*/
mark(): Mark;
/**
* Roll back to a position captured with mark().
*/
reset(mark: Mark): void;
/**
* The number of lines written since the given mark.
*/
writesSince(mark: Mark): number;
toString(): string;
private indent;
}
/**
* Quote and escape a string field value as a double-quoted text format literal.
*
* Uses the single escaping decision in escapeCodePoint, so string fields, bytes
* fields, and unknown length-delimited rendering can never drift apart. Valid
* non-ASCII passes through as raw UTF-8; surrogates are never escaped.
*/
export declare function quoteString(value: string): string;
/**
* Quote and escape a bytes field value as a double-quoted text format literal.
*
* Valid UTF-8 runs are emitted with escapeCodePoint (so they read identically
* to a string field); any byte that is not part of a valid UTF-8 sequence is
* emitted as `\xHH`, keeping the output plain ASCII that round-trips exactly.
*/
export declare function quoteBytes(value: Uint8Array): string;
export {};
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const indentUnit = " ";
/**
* A writer for the protobuf text format.
*
* The Writer owns layout: indentation, line breaks, and braces. Its output
* matches the default formatting of txtpbfmt and the multi-line output of
* protobuf-go: two-space indentation, `name: value` with a single space after
* the colon, submessages as `name: {` with the body indented and `}` aligned
* under the field name, and a trailing newline. It never inserts the randomized
* extra spaces that protobuf-go adds to discourage parsing its output as
* canonical.
*
* Output accumulates into a single buffer of lines, so a deep tree costs no
* more than the bytes it prints. To decide between `name: {}` and an indented
* block, the caller renders the body, then rolls back with mark()/reset() if it
* turned out empty — an O(1) decision that needs no separate child buffer.
*/
export class Writer {
constructor() {
this.chunks = [];
this.depth = 0;
}
/**
* Write a scalar field: `<indent>name: value` followed by a newline.
*/
scalar(name, value) {
this.chunks.push(this.indent() + name + ": " + value + "\n");
}
/**
* Write an empty message field: `<indent>name: {}` followed by a newline.
*/
emptyMessage(name) {
this.chunks.push(this.indent() + name + ": {}\n");
}
/**
* Open a message field: `<indent>name: {` followed by a newline, then indent
* the body. Close it with end().
*/
openMessage(name) {
this.chunks.push(this.indent() + name + ": {\n");
this.depth++;
}
/**
* Close a message opened with openMessage(): outdent and write `<indent>}`
* followed by a newline.
*/
end() {
this.depth--;
this.chunks.push(this.indent() + "}\n");
}
/**
* Capture the current output position so it can be rolled back with reset().
*/
mark() {
return { size: this.chunks.length, depth: this.depth };
}
/**
* Roll back to a position captured with mark().
*/
reset(mark) {
this.chunks.length = mark.size;
this.depth = mark.depth;
}
/**
* The number of lines written since the given mark.
*/
writesSince(mark) {
return this.chunks.length - mark.size;
}
toString() {
return this.chunks.join("");
}
indent() {
return indentUnit.repeat(this.depth);
}
}
/**
* Quote and escape a string field value as a double-quoted text format literal.
*
* Uses the single escaping decision in escapeCodePoint, so string fields, bytes
* fields, and unknown length-delimited rendering can never drift apart. Valid
* non-ASCII passes through as raw UTF-8; surrogates are never escaped.
*/
export function quoteString(value) {
var _a;
let out = '"';
for (const ch of value) {
out += (_a = escapeCodePoint(ch.codePointAt(0))) !== null && _a !== void 0 ? _a : ch;
}
return out + '"';
}
/**
* Quote and escape a bytes field value as a double-quoted text format literal.
*
* Valid UTF-8 runs are emitted with escapeCodePoint (so they read identically
* to a string field); any byte that is not part of a valid UTF-8 sequence is
* emitted as `\xHH`, keeping the output plain ASCII that round-trips exactly.
*/
export function quoteBytes(value) {
var _a;
let out = '"';
for (let i = 0; i < value.length;) {
const rune = decodeUtf8(value, i);
if (rune === undefined) {
out += "\\x" + hex2(value[i]);
i++;
continue;
}
out += (_a = escapeCodePoint(rune.code)) !== null && _a !== void 0 ? _a : String.fromCodePoint(rune.code);
i += rune.size;
}
return out + '"';
}
/**
* The single source of truth for escaping a code point in a text format string
* literal. Returns the escape sequence, or undefined when the code point may be
* emitted raw.
*
* Escapes the conventional sequences, all C0 controls and DEL as `\xHH`, and
* the C1 controls (U+0080–U+009F) as `\u00HH`. Surrogates and everything else
* pass through raw.
*/
function escapeCodePoint(code) {
switch (code) {
case 0x5c:
return "\\\\";
case 0x22:
return '\\"';
case 0x0a:
return "\\n";
case 0x0d:
return "\\r";
case 0x09:
return "\\t";
}
if (code < 0x20 || code === 0x7f) {
return "\\x" + hex2(code);
}
if (code >= 0x80 && code <= 0x9f) {
return "\\u" + code.toString(16).padStart(4, "0");
}
return undefined;
}
/**
* Decode the UTF-8 sequence starting at `offset`, returning the code point and
* its byte length, or undefined if the bytes there are not valid UTF-8. We
* decode manually (rather than via TextDecoder) so an invalid byte can be
* pinpointed and escaped individually.
*/
function decodeUtf8(bytes, offset) {
const b0 = bytes[offset];
if (b0 < 0x80) {
return { code: b0, size: 1 };
}
if (b0 < 0xc0) {
return undefined;
}
if (b0 < 0xe0) {
const b1 = bytes[offset + 1];
if (!isContinuation(b1)) {
return undefined;
}
const code = ((b0 & 0x1f) << 6) | (b1 & 0x3f);
return code < 0x80 ? undefined : { code, size: 2 };
}
if (b0 < 0xf0) {
const b1 = bytes[offset + 1];
const b2 = bytes[offset + 2];
if (!isContinuation(b1) || !isContinuation(b2)) {
return undefined;
}
const code = ((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f);
if (code < 0x800 || (code >= 0xd800 && code <= 0xdfff)) {
return undefined;
}
return { code, size: 3 };
}
if (b0 < 0xf8) {
const b1 = bytes[offset + 1];
const b2 = bytes[offset + 2];
const b3 = bytes[offset + 3];
if (!isContinuation(b1) || !isContinuation(b2) || !isContinuation(b3)) {
return undefined;
}
const code = ((b0 & 0x07) << 18) |
((b1 & 0x3f) << 12) |
((b2 & 0x3f) << 6) |
(b3 & 0x3f);
if (code < 0x10000 || code > 0x10ffff) {
return undefined;
}
return { code, size: 4 };
}
return undefined;
}
function isContinuation(byte) {
return byte !== undefined && (byte & 0xc0) === 0x80;
}
function hex2(value) {
return value.toString(16).padStart(2, "0");
}
import type { DescEnum } from "./descriptors.js";
import type { UnknownEnum } from "./types.js";
/**
* Open enums can contain numeric values that are not in the set of values
* defined by the enum.
*
* This function returns true for those values, and narrows the type to
* `UnknownEnum`.
*/
export declare function isUnknownEnum(desc: DescEnum, value: number): value is UnknownEnum;
// Copyright 2021-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Open enums can contain numeric values that are not in the set of values
* defined by the enum.
*
* This function returns true for those values, and narrows the type to
* `UnknownEnum`.
*/
export function isUnknownEnum(desc, value) {
return desc.value[value] === undefined;
}
+5
-0

@@ -135,2 +135,7 @@ /**

};
readonly UnknownEnum: {
readonly typeOnly: true;
readonly bootstrapWktFrom: "../../types.js";
readonly from: "@bufbuild/protobuf";
};
};
+15
-4

@@ -12,8 +12,19 @@ import type { DescEnum, DescFile } from "../descriptors.js";

* Construct a TypeScript enum object at runtime from a descriptor.
*
* The returned object is identical to a transpiled TS enum and includes the
* reverse mapping, see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
*/
export declare function tsEnum(desc: DescEnum): enumObject;
type enumObject = {
export declare function tsEnum(desc: DescEnum): {
[key: number]: string;
[k: string]: number | string;
[k: string]: string | number;
};
export {};
/**
* Construct an object enum at runtime from a descriptor.
*
* The returned object is a record of enum value name to integer value. It's
* a subset of transpiled TS enums - it does not include the reverse mapping,
* and only supports lookup by value name.
*/
export declare function objEnum(desc: DescEnum): {
[key: string]: number;
};

@@ -18,2 +18,3 @@ "use strict";

exports.tsEnum = tsEnum;
exports.objEnum = objEnum;
/**

@@ -33,2 +34,5 @@ * Hydrate an enum descriptor.

* Construct a TypeScript enum object at runtime from a descriptor.
*
* The returned object is identical to a transpiled TS enum and includes the
* reverse mapping, see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
*/

@@ -43,1 +47,15 @@ function tsEnum(desc) {

}
/**
* Construct an object enum at runtime from a descriptor.
*
* The returned object is a record of enum value name to integer value. It's
* a subset of transpiled TS enums - it does not include the reverse mapping,
* and only supports lookup by value name.
*/
function objEnum(desc) {
const enumObject = {};
for (const value of desc.values) {
enumObject[value.localName] = value.number;
}
return enumObject;
}

@@ -73,2 +73,7 @@ /**

};
readonly UnknownEnum: {
readonly typeOnly: true;
readonly bootstrapWktFrom: "../../types.js";
readonly from: "@bufbuild/protobuf";
};
readonly codegen: {

@@ -110,2 +115,7 @@ readonly boot: {

};
readonly objEnum: {
readonly typeOnly: false;
readonly bootstrapWktFrom: "../../codegenv2/enum.js";
readonly from: string;
};
readonly GenFile: {

@@ -112,0 +122,0 @@ readonly typeOnly: true;

@@ -58,2 +58,3 @@ "use strict";

JsonObject: { typeOnly: true, bootstrapWktFrom: "../../json-value.js", from: exports.packageName },
UnknownEnum: { typeOnly: true, bootstrapWktFrom: "../../types.js", from: exports.packageName },
codegen: {

@@ -67,2 +68,3 @@ boot: { typeOnly: false, bootstrapWktFrom: "../../codegenv2/boot.js", from: exports.packageName + "/codegenv2" },

tsEnum: { typeOnly: false, bootstrapWktFrom: "../../codegenv2/enum.js", from: exports.packageName + "/codegenv2" },
objEnum: { typeOnly: false, bootstrapWktFrom: "../../codegenv2/enum.js", from: exports.packageName + "/codegenv2" },
GenFile: { typeOnly: true, bootstrapWktFrom: "../../codegenv2/types.js", from: exports.packageName + "/codegenv2" },

@@ -69,0 +71,0 @@ GenEnum: { typeOnly: true, bootstrapWktFrom: "../../codegenv2/types.js", from: exports.packageName + "/codegenv2" },

@@ -23,7 +23,7 @@ "use strict";

const wrappers_js_1 = require("./wkt/wrappers.js");
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name = $number;
const EDITION_PROTO3 = 999;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name = $number;
const EDITION_PROTO2 = 998;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;

@@ -30,0 +30,0 @@ /**

import type { DescriptorProto, Edition, EnumDescriptorProto, EnumValueDescriptorProto, FeatureSet_FieldPresence, FieldDescriptorProto, FileDescriptorProto, MethodDescriptorProto, MethodOptions_IdempotencyLevel, OneofDescriptorProto, ServiceDescriptorProto } from "./wkt/gen/google/protobuf/descriptor_pb.js";
import type { ScalarValue } from "./reflect/scalar.js";
export type SupportedEdition = Extract<Edition, Edition.EDITION_PROTO2 | Edition.EDITION_PROTO3 | Edition.EDITION_2023 | Edition.EDITION_2024>;
type SupportedFieldPresence = Extract<FeatureSet_FieldPresence, FeatureSet_FieldPresence.EXPLICIT | FeatureSet_FieldPresence.IMPLICIT | FeatureSet_FieldPresence.LEGACY_REQUIRED>;
export type SupportedEdition = Extract<Edition, typeof Edition.EDITION_PROTO2 | typeof Edition.EDITION_PROTO3 | typeof Edition.EDITION_2023 | typeof Edition.EDITION_2024>;
type SupportedFieldPresence = Extract<FeatureSet_FieldPresence, typeof FeatureSet_FieldPresence.EXPLICIT | typeof FeatureSet_FieldPresence.IMPLICIT | typeof FeatureSet_FieldPresence.LEGACY_REQUIRED>;
/**

@@ -6,0 +6,0 @@ * Scalar value types. This is a subset of field types declared by protobuf

@@ -19,1 +19,2 @@ export * from "./types.js";

export * from "./proto-int64.js";
export * from "./unknown-enum.js";

@@ -55,1 +55,2 @@ "use strict";

__exportStar(require("./proto-int64.js"), exports);
__exportStar(require("./unknown-enum.js"), exports);

@@ -28,2 +28,4 @@ "use strict";

const guard_js_1 = require("./guard.js");
// google.protobuf.NullValue.NULL_VALUE;
const NULL_VALUE = 0;
/**

@@ -519,4 +521,3 @@ * Create a ReflectMessage.

if (json === null) {
const nullValue = 0;
value.kind = { case: "nullValue", value: nullValue };
value.kind = { case: "nullValue", value: NULL_VALUE };
}

@@ -523,0 +524,0 @@ else if (Array.isArray(json)) {

@@ -24,3 +24,3 @@ "use strict";

const scalar_js_1 = require("./scalar.js");
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;

@@ -27,0 +27,0 @@ exports.unsafeLocal = Symbol.for("reflect unsafe local");

@@ -196,39 +196,39 @@ "use strict";

}
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name = $number;
const EDITION_PROTO2 = 998;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name = $number;
const EDITION_PROTO3 = 999;
// bootstrap-inject google.protobuf.Edition.EDITION_UNSTABLE: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_UNSTABLE: const $name = $number;
const EDITION_UNSTABLE = 9999;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_STRING: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_STRING: const $name = $number;
const TYPE_STRING = 9;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_GROUP: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_GROUP: const $name = $number;
const TYPE_GROUP = 10;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_MESSAGE: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_MESSAGE: const $name = $number;
const TYPE_MESSAGE = 11;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_BYTES: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_BYTES: const $name = $number;
const TYPE_BYTES = 12;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_ENUM: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_ENUM: const $name = $number;
const TYPE_ENUM = 14;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REPEATED: const $name: FieldDescriptorProto_Label.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REPEATED: const $name = $number;
const LABEL_REPEATED = 3;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REQUIRED: const $name: FieldDescriptorProto_Label.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REQUIRED: const $name = $number;
const LABEL_REQUIRED = 2;
// bootstrap-inject google.protobuf.FieldOptions.JSType.JS_STRING: const $name: FieldOptions_JSType.$localName = $number;
// bootstrap-inject google.protobuf.FieldOptions.JSType.JS_STRING: const $name = $number;
const JS_STRING = 1;
// bootstrap-inject google.protobuf.MethodOptions.IdempotencyLevel.IDEMPOTENCY_UNKNOWN: const $name: MethodOptions_IdempotencyLevel.$localName = $number;
// bootstrap-inject google.protobuf.MethodOptions.IdempotencyLevel.IDEMPOTENCY_UNKNOWN: const $name = $number;
const IDEMPOTENCY_UNKNOWN = 0;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.EXPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.EXPLICIT: const $name = $number;
const EXPLICIT = 1;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number;
const LEGACY_REQUIRED = 3;
// bootstrap-inject google.protobuf.FeatureSet.RepeatedFieldEncoding.PACKED: const $name: FeatureSet_RepeatedFieldEncoding.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.RepeatedFieldEncoding.PACKED: const $name = $number;
const PACKED = 1;
// bootstrap-inject google.protobuf.FeatureSet.MessageEncoding.DELIMITED: const $name: FeatureSet_MessageEncoding.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.MessageEncoding.DELIMITED: const $name = $number;
const DELIMITED = 2;
// bootstrap-inject google.protobuf.FeatureSet.EnumType.OPEN: const $name: FeatureSet_EnumType.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.EnumType.OPEN: const $name = $number;
const OPEN = 1;
// bootstrap-inject google.protobuf.FeatureSet.Utf8Validation.VERIFY: const $name: FeatureSet_Utf8Validation.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.Utf8Validation.VERIFY: const $name = $number;
const VERIFY = 2;

@@ -235,0 +235,0 @@ // biome-ignore format: want this to read well

@@ -21,3 +21,3 @@ "use strict";

const descriptors_js_1 = require("./descriptors.js");
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number;
const LEGACY_REQUIRED = 3;

@@ -24,0 +24,0 @@ // Default options for serializing binary data.

@@ -27,5 +27,5 @@ "use strict";

const reflect_check_js_1 = require("./reflect/reflect-check.js");
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number;
const LEGACY_REQUIRED = 3;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;

@@ -32,0 +32,0 @@ // Default options for serializing to JSON.

@@ -75,2 +75,9 @@ import type { GenEnum as GenEnumV1, GenExtension as GenExtensionV1, GenMessage as GenMessageV1 } from "./codegenv1/types.js";

/**
* An unknown enum is a value that's not in the set of values defined by an
* enum. Open Protobuf enums may
*/
export type UnknownEnum = number & {
__unknown_enum: true;
};
/**
* Describes a streaming RPC declaration.

@@ -77,0 +84,0 @@ */

@@ -16,2 +16,16 @@ import type { DescMessage } from "../descriptors.js";

/**
* Options for parsing size-delimited messages from a stream.
*/
export interface SizeDelimitedDecodeOptions extends BinaryReadOptions {
/**
* Limit the size of a single message in the stream, in bytes.
*
* If a message in the stream declares a size exceeding this limit, an
* error is raised before the message is buffered.
*
* The default limit is 64 MiB.
*/
readMaxBytes: number;
}
/**
* Parse a stream of size-delimited messages.

@@ -24,4 +38,8 @@ *

* For details, see https://github.com/protocolbuffers/protobuf/issues/10229
*
* Messages exceeding the limit given with the option readMaxBytes raise an
* error. The limit is 64 MiB by default. All other options are the
* standard binary read options, passed through to decode each message.
*/
export declare function sizeDelimitedDecodeStream<Desc extends DescMessage>(messageDesc: Desc, iterable: AsyncIterable<Uint8Array>, options?: Partial<BinaryReadOptions>): AsyncIterableIterator<MessageShape<Desc>>;
export declare function sizeDelimitedDecodeStream<Desc extends DescMessage>(messageDesc: Desc, iterable: AsyncIterable<Uint8Array>, options?: Partial<SizeDelimitedDecodeOptions>): AsyncIterableIterator<MessageShape<Desc>>;
/**

@@ -28,0 +46,0 @@ * Decodes the size from the given size-delimited message, which may be

@@ -56,3 +56,31 @@ "use strict";

}
// Default for SizeDelimitedDecodeOptions.readMaxBytes.
const defaultReadMaxBytes = 64 * 1024 * 1024; // 64 MiB
/**
* A growable byte buffer. Used in place of a resizable ArrayBuffer, which is
* not widely available.
*/
class ByteBuffer {
constructor() {
this.buffer = new Uint8Array(0);
this.length = 0;
}
get byteLength() {
return this.length;
}
bytes() {
return this.buffer.subarray(0, this.length);
}
append(chunk) {
const newByteLength = this.length + chunk.byteLength;
if (newByteLength > this.buffer.byteLength) {
const grown = new Uint8Array(Math.max(this.buffer.byteLength * 2, newByteLength));
grown.set(this.buffer.subarray(0, this.length));
this.buffer = grown;
}
this.buffer.set(chunk, this.length);
this.length += chunk.byteLength;
}
}
/**
* Parse a stream of size-delimited messages.

@@ -65,2 +93,6 @@ *

* For details, see https://github.com/protocolbuffers/protobuf/issues/10229
*
* Messages exceeding the limit given with the option readMaxBytes raise an
* error. The limit is 64 MiB by default. All other options are the
* standard binary read options, passed through to decode each message.
*/

@@ -70,18 +102,15 @@ function sizeDelimitedDecodeStream(messageDesc, iterable, options) {

var _a, e_1, _b, _c;
// append chunk to buffer, returning updated buffer
function append(buffer, chunk) {
const n = new Uint8Array(buffer.byteLength + chunk.byteLength);
n.set(buffer);
n.set(chunk, buffer.length);
return n;
}
let buffer = new Uint8Array(0);
var _d;
const readMaxBytes = (_d = options === null || options === void 0 ? void 0 : options.readMaxBytes) !== null && _d !== void 0 ? _d : defaultReadMaxBytes;
let buffer = new ByteBuffer();
try {
for (var _d = true, iterable_1 = __asyncValues(iterable), iterable_1_1; iterable_1_1 = yield __await(iterable_1.next()), _a = iterable_1_1.done, !_a; _d = true) {
for (var _e = true, iterable_1 = __asyncValues(iterable), iterable_1_1; iterable_1_1 = yield __await(iterable_1.next()), _a = iterable_1_1.done, !_a; _e = true) {
_c = iterable_1_1.value;
_d = false;
_e = false;
const chunk = _c;
buffer = append(buffer, chunk);
buffer.append(chunk);
const bytes = buffer.bytes();
let offset = 0;
for (;;) {
const size = sizeDelimitedPeek(buffer);
const size = sizeDelimitedPeek(bytes.subarray(offset));
if (size.eof) {

@@ -91,9 +120,18 @@ // size is incomplete, buffer more data

}
if (size.offset + size.size > buffer.byteLength) {
if (size.size > readMaxBytes) {
throw new Error(`message size ${size.size} is larger than configured readMaxBytes ${readMaxBytes}`);
}
const messageStart = offset + size.offset;
const messageEnd = messageStart + size.size;
if (messageEnd > bytes.byteLength) {
// message is incomplete, buffer more data
break;
}
yield yield __await((0, from_binary_js_1.fromBinary)(messageDesc, buffer.subarray(size.offset, size.offset + size.size), options));
buffer = buffer.subarray(size.offset + size.size);
yield yield __await((0, from_binary_js_1.fromBinary)(messageDesc, bytes.subarray(messageStart, messageEnd), options));
offset = messageEnd;
}
if (offset > 0) {
buffer = new ByteBuffer();
buffer.append(bytes.subarray(offset));
}
}

@@ -104,3 +142,3 @@ }

try {
if (!_d && !_a && (_b = iterable_1.return)) yield __await(_b.call(iterable_1));
if (!_e && !_a && (_b = iterable_1.return)) yield __await(_b.call(iterable_1));
}

@@ -107,0 +145,0 @@ finally { if (e_1) throw e_1.error; }

@@ -135,2 +135,7 @@ /**

};
readonly UnknownEnum: {
readonly typeOnly: true;
readonly bootstrapWktFrom: "../../types.js";
readonly from: "@bufbuild/protobuf";
};
};

@@ -12,8 +12,19 @@ import type { DescEnum, DescFile } from "../descriptors.js";

* Construct a TypeScript enum object at runtime from a descriptor.
*
* The returned object is identical to a transpiled TS enum and includes the
* reverse mapping, see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
*/
export declare function tsEnum(desc: DescEnum): enumObject;
type enumObject = {
export declare function tsEnum(desc: DescEnum): {
[key: number]: string;
[k: string]: number | string;
[k: string]: string | number;
};
export {};
/**
* Construct an object enum at runtime from a descriptor.
*
* The returned object is a record of enum value name to integer value. It's
* a subset of transpiled TS enums - it does not include the reverse mapping,
* and only supports lookup by value name.
*/
export declare function objEnum(desc: DescEnum): {
[key: string]: number;
};

@@ -28,2 +28,5 @@ // Copyright 2021-2026 Buf Technologies, Inc.

* Construct a TypeScript enum object at runtime from a descriptor.
*
* The returned object is identical to a transpiled TS enum and includes the
* reverse mapping, see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
*/

@@ -38,1 +41,15 @@ export function tsEnum(desc) {

}
/**
* Construct an object enum at runtime from a descriptor.
*
* The returned object is a record of enum value name to integer value. It's
* a subset of transpiled TS enums - it does not include the reverse mapping,
* and only supports lookup by value name.
*/
export function objEnum(desc) {
const enumObject = {};
for (const value of desc.values) {
enumObject[value.localName] = value.number;
}
return enumObject;
}

@@ -73,2 +73,7 @@ /**

};
readonly UnknownEnum: {
readonly typeOnly: true;
readonly bootstrapWktFrom: "../../types.js";
readonly from: "@bufbuild/protobuf";
};
readonly codegen: {

@@ -110,2 +115,7 @@ readonly boot: {

};
readonly objEnum: {
readonly typeOnly: false;
readonly bootstrapWktFrom: "../../codegenv2/enum.js";
readonly from: string;
};
readonly GenFile: {

@@ -112,0 +122,0 @@ readonly typeOnly: true;

@@ -55,2 +55,3 @@ // Copyright 2021-2026 Buf Technologies, Inc.

JsonObject: { typeOnly: true, bootstrapWktFrom: "../../json-value.js", from: packageName },
UnknownEnum: { typeOnly: true, bootstrapWktFrom: "../../types.js", from: packageName },
codegen: {

@@ -64,2 +65,3 @@ boot: { typeOnly: false, bootstrapWktFrom: "../../codegenv2/boot.js", from: packageName + "/codegenv2" },

tsEnum: { typeOnly: false, bootstrapWktFrom: "../../codegenv2/enum.js", from: packageName + "/codegenv2" },
objEnum: { typeOnly: false, bootstrapWktFrom: "../../codegenv2/enum.js", from: packageName + "/codegenv2" },
GenFile: { typeOnly: true, bootstrapWktFrom: "../../codegenv2/types.js", from: packageName + "/codegenv2" },

@@ -66,0 +68,0 @@ GenEnum: { typeOnly: true, bootstrapWktFrom: "../../codegenv2/types.js", from: packageName + "/codegenv2" },

@@ -20,7 +20,7 @@ // Copyright 2021-2026 Buf Technologies, Inc.

import { isWrapperDesc } from "./wkt/wrappers.js";
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name = $number;
const EDITION_PROTO3 = 999;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name = $number;
const EDITION_PROTO2 = 998;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;

@@ -27,0 +27,0 @@ /**

import type { DescriptorProto, Edition, EnumDescriptorProto, EnumValueDescriptorProto, FeatureSet_FieldPresence, FieldDescriptorProto, FileDescriptorProto, MethodDescriptorProto, MethodOptions_IdempotencyLevel, OneofDescriptorProto, ServiceDescriptorProto } from "./wkt/gen/google/protobuf/descriptor_pb.js";
import type { ScalarValue } from "./reflect/scalar.js";
export type SupportedEdition = Extract<Edition, Edition.EDITION_PROTO2 | Edition.EDITION_PROTO3 | Edition.EDITION_2023 | Edition.EDITION_2024>;
type SupportedFieldPresence = Extract<FeatureSet_FieldPresence, FeatureSet_FieldPresence.EXPLICIT | FeatureSet_FieldPresence.IMPLICIT | FeatureSet_FieldPresence.LEGACY_REQUIRED>;
export type SupportedEdition = Extract<Edition, typeof Edition.EDITION_PROTO2 | typeof Edition.EDITION_PROTO3 | typeof Edition.EDITION_2023 | typeof Edition.EDITION_2024>;
type SupportedFieldPresence = Extract<FeatureSet_FieldPresence, typeof FeatureSet_FieldPresence.EXPLICIT | typeof FeatureSet_FieldPresence.IMPLICIT | typeof FeatureSet_FieldPresence.LEGACY_REQUIRED>;
/**

@@ -6,0 +6,0 @@ * Scalar value types. This is a subset of field types declared by protobuf

@@ -19,1 +19,2 @@ export * from "./types.js";

export * from "./proto-int64.js";
export * from "./unknown-enum.js";

@@ -29,1 +29,2 @@ // Copyright 2021-2026 Buf Technologies, Inc.

export * from "./proto-int64.js";
export * from "./unknown-enum.js";

@@ -23,2 +23,4 @@ // Copyright 2021-2026 Buf Technologies, Inc.

import { isObject, isReflectList, isReflectMap, isReflectMessage, } from "./guard.js";
// google.protobuf.NullValue.NULL_VALUE;
const NULL_VALUE = 0;
/**

@@ -514,4 +516,3 @@ * Create a ReflectMessage.

if (json === null) {
const nullValue = 0;
value.kind = { case: "nullValue", value: nullValue };
value.kind = { case: "nullValue", value: NULL_VALUE };
}

@@ -518,0 +519,0 @@ else if (Array.isArray(json)) {

@@ -15,3 +15,3 @@ // Copyright 2021-2026 Buf Technologies, Inc.

import { isScalarZeroValue, scalarZeroValue } from "./scalar.js";
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;

@@ -18,0 +18,0 @@ export const unsafeLocal = Symbol.for("reflect unsafe local");

@@ -190,39 +190,39 @@ // Copyright 2021-2026 Buf Technologies, Inc.

}
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name = $number;
const EDITION_PROTO2 = 998;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name = $number;
const EDITION_PROTO3 = 999;
// bootstrap-inject google.protobuf.Edition.EDITION_UNSTABLE: const $name: Edition.$localName = $number;
// bootstrap-inject google.protobuf.Edition.EDITION_UNSTABLE: const $name = $number;
const EDITION_UNSTABLE = 9999;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_STRING: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_STRING: const $name = $number;
const TYPE_STRING = 9;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_GROUP: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_GROUP: const $name = $number;
const TYPE_GROUP = 10;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_MESSAGE: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_MESSAGE: const $name = $number;
const TYPE_MESSAGE = 11;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_BYTES: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_BYTES: const $name = $number;
const TYPE_BYTES = 12;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_ENUM: const $name: FieldDescriptorProto_Type.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_ENUM: const $name = $number;
const TYPE_ENUM = 14;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REPEATED: const $name: FieldDescriptorProto_Label.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REPEATED: const $name = $number;
const LABEL_REPEATED = 3;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REQUIRED: const $name: FieldDescriptorProto_Label.$localName = $number;
// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REQUIRED: const $name = $number;
const LABEL_REQUIRED = 2;
// bootstrap-inject google.protobuf.FieldOptions.JSType.JS_STRING: const $name: FieldOptions_JSType.$localName = $number;
// bootstrap-inject google.protobuf.FieldOptions.JSType.JS_STRING: const $name = $number;
const JS_STRING = 1;
// bootstrap-inject google.protobuf.MethodOptions.IdempotencyLevel.IDEMPOTENCY_UNKNOWN: const $name: MethodOptions_IdempotencyLevel.$localName = $number;
// bootstrap-inject google.protobuf.MethodOptions.IdempotencyLevel.IDEMPOTENCY_UNKNOWN: const $name = $number;
const IDEMPOTENCY_UNKNOWN = 0;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.EXPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.EXPLICIT: const $name = $number;
const EXPLICIT = 1;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number;
const LEGACY_REQUIRED = 3;
// bootstrap-inject google.protobuf.FeatureSet.RepeatedFieldEncoding.PACKED: const $name: FeatureSet_RepeatedFieldEncoding.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.RepeatedFieldEncoding.PACKED: const $name = $number;
const PACKED = 1;
// bootstrap-inject google.protobuf.FeatureSet.MessageEncoding.DELIMITED: const $name: FeatureSet_MessageEncoding.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.MessageEncoding.DELIMITED: const $name = $number;
const DELIMITED = 2;
// bootstrap-inject google.protobuf.FeatureSet.EnumType.OPEN: const $name: FeatureSet_EnumType.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.EnumType.OPEN: const $name = $number;
const OPEN = 1;
// bootstrap-inject google.protobuf.FeatureSet.Utf8Validation.VERIFY: const $name: FeatureSet_Utf8Validation.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.Utf8Validation.VERIFY: const $name = $number;
const VERIFY = 2;

@@ -229,0 +229,0 @@ // biome-ignore format: want this to read well

@@ -17,3 +17,3 @@ // Copyright 2021-2026 Buf Technologies, Inc.

import { ScalarType } from "./descriptors.js";
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number;
const LEGACY_REQUIRED = 3;

@@ -20,0 +20,0 @@ // Default options for serializing binary data.

@@ -22,5 +22,5 @@ // Copyright 2021-2026 Buf Technologies, Inc.

import { checkField, formatVal } from "./reflect/reflect-check.js";
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name = $number;
const LEGACY_REQUIRED = 3;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name = $number;
const IMPLICIT = 2;

@@ -27,0 +27,0 @@ // Default options for serializing to JSON.

@@ -75,2 +75,9 @@ import type { GenEnum as GenEnumV1, GenExtension as GenExtensionV1, GenMessage as GenMessageV1 } from "./codegenv1/types.js";

/**
* An unknown enum is a value that's not in the set of values defined by an
* enum. Open Protobuf enums may
*/
export type UnknownEnum = number & {
__unknown_enum: true;
};
/**
* Describes a streaming RPC declaration.

@@ -77,0 +84,0 @@ */

@@ -16,2 +16,16 @@ import type { DescMessage } from "../descriptors.js";

/**
* Options for parsing size-delimited messages from a stream.
*/
export interface SizeDelimitedDecodeOptions extends BinaryReadOptions {
/**
* Limit the size of a single message in the stream, in bytes.
*
* If a message in the stream declares a size exceeding this limit, an
* error is raised before the message is buffered.
*
* The default limit is 64 MiB.
*/
readMaxBytes: number;
}
/**
* Parse a stream of size-delimited messages.

@@ -24,4 +38,8 @@ *

* For details, see https://github.com/protocolbuffers/protobuf/issues/10229
*
* Messages exceeding the limit given with the option readMaxBytes raise an
* error. The limit is 64 MiB by default. All other options are the
* standard binary read options, passed through to decode each message.
*/
export declare function sizeDelimitedDecodeStream<Desc extends DescMessage>(messageDesc: Desc, iterable: AsyncIterable<Uint8Array>, options?: Partial<BinaryReadOptions>): AsyncIterableIterator<MessageShape<Desc>>;
export declare function sizeDelimitedDecodeStream<Desc extends DescMessage>(messageDesc: Desc, iterable: AsyncIterable<Uint8Array>, options?: Partial<SizeDelimitedDecodeOptions>): AsyncIterableIterator<MessageShape<Desc>>;
/**

@@ -28,0 +46,0 @@ * Decodes the size from the given size-delimited message, which may be

@@ -51,3 +51,31 @@ // Copyright 2021-2026 Buf Technologies, Inc.

}
// Default for SizeDelimitedDecodeOptions.readMaxBytes.
const defaultReadMaxBytes = 64 * 1024 * 1024; // 64 MiB
/**
* A growable byte buffer. Used in place of a resizable ArrayBuffer, which is
* not widely available.
*/
class ByteBuffer {
constructor() {
this.buffer = new Uint8Array(0);
this.length = 0;
}
get byteLength() {
return this.length;
}
bytes() {
return this.buffer.subarray(0, this.length);
}
append(chunk) {
const newByteLength = this.length + chunk.byteLength;
if (newByteLength > this.buffer.byteLength) {
const grown = new Uint8Array(Math.max(this.buffer.byteLength * 2, newByteLength));
grown.set(this.buffer.subarray(0, this.length));
this.buffer = grown;
}
this.buffer.set(chunk, this.length);
this.length += chunk.byteLength;
}
}
/**
* Parse a stream of size-delimited messages.

@@ -60,2 +88,6 @@ *

* For details, see https://github.com/protocolbuffers/protobuf/issues/10229
*
* Messages exceeding the limit given with the option readMaxBytes raise an
* error. The limit is 64 MiB by default. All other options are the
* standard binary read options, passed through to decode each message.
*/

@@ -65,18 +97,15 @@ export function sizeDelimitedDecodeStream(messageDesc, iterable, options) {

var _a, e_1, _b, _c;
// append chunk to buffer, returning updated buffer
function append(buffer, chunk) {
const n = new Uint8Array(buffer.byteLength + chunk.byteLength);
n.set(buffer);
n.set(chunk, buffer.length);
return n;
}
let buffer = new Uint8Array(0);
var _d;
const readMaxBytes = (_d = options === null || options === void 0 ? void 0 : options.readMaxBytes) !== null && _d !== void 0 ? _d : defaultReadMaxBytes;
let buffer = new ByteBuffer();
try {
for (var _d = true, iterable_1 = __asyncValues(iterable), iterable_1_1; iterable_1_1 = yield __await(iterable_1.next()), _a = iterable_1_1.done, !_a; _d = true) {
for (var _e = true, iterable_1 = __asyncValues(iterable), iterable_1_1; iterable_1_1 = yield __await(iterable_1.next()), _a = iterable_1_1.done, !_a; _e = true) {
_c = iterable_1_1.value;
_d = false;
_e = false;
const chunk = _c;
buffer = append(buffer, chunk);
buffer.append(chunk);
const bytes = buffer.bytes();
let offset = 0;
for (;;) {
const size = sizeDelimitedPeek(buffer);
const size = sizeDelimitedPeek(bytes.subarray(offset));
if (size.eof) {

@@ -86,9 +115,18 @@ // size is incomplete, buffer more data

}
if (size.offset + size.size > buffer.byteLength) {
if (size.size > readMaxBytes) {
throw new Error(`message size ${size.size} is larger than configured readMaxBytes ${readMaxBytes}`);
}
const messageStart = offset + size.offset;
const messageEnd = messageStart + size.size;
if (messageEnd > bytes.byteLength) {
// message is incomplete, buffer more data
break;
}
yield yield __await(fromBinary(messageDesc, buffer.subarray(size.offset, size.offset + size.size), options));
buffer = buffer.subarray(size.offset + size.size);
yield yield __await(fromBinary(messageDesc, bytes.subarray(messageStart, messageEnd), options));
offset = messageEnd;
}
if (offset > 0) {
buffer = new ByteBuffer();
buffer.append(bytes.subarray(offset));
}
}

@@ -99,3 +137,3 @@ }

try {
if (!_d && !_a && (_b = iterable_1.return)) yield __await(_b.call(iterable_1));
if (!_e && !_a && (_b = iterable_1.return)) yield __await(_b.call(iterable_1));
}

@@ -102,0 +140,0 @@ finally { if (e_1) throw e_1.error; }

{
"name": "@bufbuild/protobuf",
"version": "2.12.1",
"version": "2.13.0",
"license": "(Apache-2.0 AND BSD-3-Clause)",

@@ -32,2 +32,3 @@ "description": "Protocol Buffers for ECMAScript. The only JavaScript Protobuf library that is fully-compliant with Protobuf conformance tests.",

"tshy": {
"selfLink": false,
"exports": {

@@ -40,3 +41,3 @@ ".": "./src/index.ts",

"./wire": "./src/wire/index.ts",
"./package.json": "./package.json"
"./txtpb": "./src/txtpb/index.ts"
}

@@ -112,3 +113,12 @@ },

},
"./package.json": "./package.json"
"./txtpb": {
"import": {
"types": "./dist/esm/txtpb/index.d.ts",
"default": "./dist/esm/txtpb/index.js"
},
"require": {
"types": "./dist/commonjs/txtpb/index.d.ts",
"default": "./dist/commonjs/txtpb/index.js"
}
}
},

@@ -134,2 +144,5 @@ "main": "./dist/commonjs/index.js",

"./dist/commonjs/wire/index.d.ts"
],
"txtpb": [
"./dist/commonjs/txtpb/index.d.ts"
]

@@ -136,0 +149,0 @@ }

@@ -9,3 +9,3 @@ # @bufbuild/protobuf

A complete implementation of [Protocol Buffers](https://protobuf.dev/) in TypeScript,
suitable for web browsers, Node.js, and Deno, created by [Buf](https://buf.build).
suitable for web browsers, Node.js, Deno, and Bun, created by [Buf](https://buf.build).

@@ -23,3 +23,3 @@ **Protobuf-ES** is a solid, modern alternative to existing Protobuf implementations for the JavaScript ecosystem. It's

- Implementation of all proto3 features, including the [canonical JSON format](https://protobuf.dev/programming-guides/proto3/#json)
- Implementation of all proto2 features, except for extensions and the text format
- Implementation of all proto2 features, including extensions and the text format
- Usage of standard JavaScript APIs instead of the [Closure Library](http://googlecode.blogspot.com/2009/11/introducing-closure-tools.html)

@@ -26,0 +26,0 @@ - Compatibility is covered by the Protocol Buffers [conformance tests](https://github.com/bufbuild/protobuf-es/tree/main/packages/protobuf-conformance/)