@bufbuild/protobuf
Advanced tools
| 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; | ||
| } |
@@ -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; | ||
| }; |
@@ -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"); |
+19
-19
@@ -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; } |
+16
-3
| { | ||
| "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 @@ } |
+2
-2
@@ -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/) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
1794870
9.88%336
9.09%46762
10.4%0
-100%