@bufbuild/protobuf 2.12.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +2 -2
  2. package/dist/commonjs/codegenv1/symbols.d.ts +5 -0
  3. package/dist/commonjs/codegenv2/enum.d.ts +15 -4
  4. package/dist/commonjs/codegenv2/enum.js +18 -0
  5. package/dist/commonjs/codegenv2/symbols.d.ts +10 -0
  6. package/dist/commonjs/codegenv2/symbols.js +2 -0
  7. package/dist/commonjs/create.js +3 -3
  8. package/dist/commonjs/descriptors.d.ts +2 -2
  9. package/dist/commonjs/index.d.ts +1 -0
  10. package/dist/commonjs/index.js +1 -0
  11. package/dist/commonjs/reflect/reflect.js +3 -2
  12. package/dist/commonjs/reflect/unsafe.js +1 -1
  13. package/dist/commonjs/registry.js +19 -19
  14. package/dist/commonjs/to-binary.js +1 -1
  15. package/dist/commonjs/to-json.js +2 -2
  16. package/dist/commonjs/txtpb/from-text.d.ts +35 -0
  17. package/dist/commonjs/txtpb/from-text.js +684 -0
  18. package/dist/commonjs/txtpb/index.d.ts +4 -0
  19. package/dist/commonjs/txtpb/index.js +21 -0
  20. package/dist/commonjs/txtpb/is-group-like.d.ts +19 -0
  21. package/dist/commonjs/txtpb/is-group-like.js +47 -0
  22. package/dist/commonjs/txtpb/reader.d.ts +91 -0
  23. package/dist/commonjs/txtpb/reader.js +481 -0
  24. package/dist/commonjs/txtpb/to-text.d.ts +34 -0
  25. package/dist/commonjs/txtpb/to-text.js +358 -0
  26. package/dist/commonjs/txtpb/writer.d.ts +76 -0
  27. package/dist/commonjs/txtpb/writer.js +220 -0
  28. package/dist/commonjs/types.d.ts +7 -0
  29. package/dist/commonjs/unknown-enum.d.ts +10 -0
  30. package/dist/commonjs/unknown-enum.js +26 -0
  31. package/dist/commonjs/wire/size-delimited.d.ts +19 -1
  32. package/dist/commonjs/wire/size-delimited.js +54 -16
  33. package/dist/esm/codegenv1/symbols.d.ts +5 -0
  34. package/dist/esm/codegenv2/enum.d.ts +15 -4
  35. package/dist/esm/codegenv2/enum.js +17 -0
  36. package/dist/esm/codegenv2/symbols.d.ts +10 -0
  37. package/dist/esm/codegenv2/symbols.js +2 -0
  38. package/dist/esm/create.js +3 -3
  39. package/dist/esm/descriptors.d.ts +2 -2
  40. package/dist/esm/index.d.ts +1 -0
  41. package/dist/esm/index.js +1 -0
  42. package/dist/esm/reflect/reflect.js +3 -2
  43. package/dist/esm/reflect/unsafe.js +1 -1
  44. package/dist/esm/registry.js +19 -19
  45. package/dist/esm/to-binary.js +1 -1
  46. package/dist/esm/to-json.js +2 -2
  47. package/dist/esm/txtpb/from-text.d.ts +35 -0
  48. package/dist/esm/txtpb/from-text.js +680 -0
  49. package/dist/esm/txtpb/index.d.ts +4 -0
  50. package/dist/esm/txtpb/index.js +15 -0
  51. package/dist/esm/txtpb/is-group-like.d.ts +19 -0
  52. package/dist/esm/txtpb/is-group-like.js +44 -0
  53. package/dist/esm/txtpb/reader.d.ts +91 -0
  54. package/dist/esm/txtpb/reader.js +477 -0
  55. package/dist/esm/txtpb/to-text.d.ts +34 -0
  56. package/dist/esm/txtpb/to-text.js +355 -0
  57. package/dist/esm/txtpb/writer.d.ts +76 -0
  58. package/dist/esm/txtpb/writer.js +214 -0
  59. package/dist/esm/types.d.ts +7 -0
  60. package/dist/esm/unknown-enum.d.ts +10 -0
  61. package/dist/esm/unknown-enum.js +23 -0
  62. package/dist/esm/wire/size-delimited.d.ts +19 -1
  63. package/dist/esm/wire/size-delimited.js +54 -16
  64. package/package.json +16 -3
@@ -0,0 +1,19 @@
1
+ import type { DescField, DescMessage } from "../descriptors.js";
2
+ /**
3
+ * Returns true if the field is structured like a proto2 group: a delimited
4
+ * message field whose name is the lowercase of its message type name, declared
5
+ * in the same scope as that message.
6
+ *
7
+ * The text format addresses such fields by their message type name (e.g.
8
+ * `MyGroup`) rather than their field name. This is a faithful port of
9
+ * protobuf-go's isGroupLike (internal/filedesc/desc.go), so editions delimited
10
+ * fields are treated exactly like proto2 groups.
11
+ *
12
+ * Testing `field.message` first narrows the DescField union to its three
13
+ * message-bearing variants (singular, list, and map value) — all of which carry
14
+ * `delimitedEncoding` — so it is in scope below without a cast. Maps are
15
+ * excluded automatically, because their `delimitedEncoding` is always false.
16
+ */
17
+ export declare function isGroupLike(field: DescField): field is DescField & {
18
+ message: DescMessage;
19
+ };
@@ -0,0 +1,44 @@
1
+ // Copyright 2021-2026 Buf Technologies, Inc.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ /**
15
+ * Returns true if the field is structured like a proto2 group: a delimited
16
+ * message field whose name is the lowercase of its message type name, declared
17
+ * in the same scope as that message.
18
+ *
19
+ * The text format addresses such fields by their message type name (e.g.
20
+ * `MyGroup`) rather than their field name. This is a faithful port of
21
+ * protobuf-go's isGroupLike (internal/filedesc/desc.go), so editions delimited
22
+ * fields are treated exactly like proto2 groups.
23
+ *
24
+ * Testing `field.message` first narrows the DescField union to its three
25
+ * message-bearing variants (singular, list, and map value) — all of which carry
26
+ * `delimitedEncoding` — so it is in scope below without a cast. Maps are
27
+ * excluded automatically, because their `delimitedEncoding` is always false.
28
+ */
29
+ export function isGroupLike(field) {
30
+ // Groups are always delimited-encoded message fields.
31
+ if (field.message === undefined || !field.delimitedEncoding) {
32
+ return false;
33
+ }
34
+ // Group fields are always named after the lowercase message type name.
35
+ if (field.message.name.toLowerCase() !== field.name) {
36
+ return false;
37
+ }
38
+ // Groups can only be defined in the file they are used in.
39
+ if (field.message.file !== field.parent.file) {
40
+ return false;
41
+ }
42
+ // Group messages are always defined in the same scope as the field.
43
+ return field.message.parent === field.parent;
44
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * A lexical token of the protobuf text format.
3
+ *
4
+ * This is a discriminated union keyed by `type`: punctuation tokens carry no
5
+ * payload, identifiers carry their text, string tokens carry their decoded
6
+ * bytes (the same bytes back both string and bytes fields, and bytes fields may
7
+ * hold sequences that are not valid UTF-8), and numbers carry their literal
8
+ * text plus enough classification for the parser to accept or reject them per
9
+ * field type.
10
+ *
11
+ * The minus sign before a number is its own token rather than part of the
12
+ * number, which keeps numeric sign handling in one place in the parser and,
13
+ * because whitespace and comments between tokens are insignificant, makes
14
+ * `- 42` mean `-42`, matching protobuf-go (decode_number.go). A minus glued to
15
+ * a letter is instead folded into a negative identifier (`-inf`/`-infinity`),
16
+ * because protobuf-go requires the sign glued for those literals — `- inf` is
17
+ * an error there, not negative infinity.
18
+ */
19
+ export type Token = {
20
+ readonly type: Structural | "eof";
21
+ } | {
22
+ readonly type: "identifier";
23
+ readonly value: string;
24
+ } | {
25
+ readonly type: "string";
26
+ readonly value: Uint8Array;
27
+ } | {
28
+ readonly type: "int";
29
+ readonly text: string;
30
+ readonly base: 8 | 10 | 16;
31
+ } | {
32
+ readonly type: "float";
33
+ readonly text: string;
34
+ };
35
+ /**
36
+ * The structural tokens, each the literal source character it represents.
37
+ */
38
+ type Structural = "{" | "}" | "<" | ">" | "[" | "]" | ":" | "," | ";" | "-";
39
+ /**
40
+ * A tokenizer for the protobuf text format.
41
+ *
42
+ * The parser drives it with `peek()` and `next()` (one-token lookahead) and,
43
+ * once it knows it is in a field-name position, asks for the contents of a
44
+ * bracketed name with `readTypeName()` — the `[...]` syntax for extensions and
45
+ * Any type URLs is ambiguous with the list syntax at the lexical level. The
46
+ * structure is modeled on the graphql-js lexer: a single scan position and a
47
+ * per-token reader that returns the decoded value.
48
+ */
49
+ export declare class Reader {
50
+ private readonly input;
51
+ private readonly length;
52
+ private pos;
53
+ private lookahead;
54
+ constructor(input: string);
55
+ /**
56
+ * Return the next token without consuming it.
57
+ */
58
+ peek(): Token;
59
+ /**
60
+ * Consume and return the next token.
61
+ */
62
+ next(): Token;
63
+ /**
64
+ * Read the contents of a bracketed name, used for extension fields and the
65
+ * expanded form of google.protobuf.Any. The opening `[` must already have
66
+ * been consumed with `next()`. Whitespace and comments inside the brackets
67
+ * are insignificant. Returns the inner name with the brackets removed, e.g.
68
+ * "pkg.Message.field" or "type.googleapis.com/pkg.Message".
69
+ *
70
+ * The text format grammar for this is incomplete, so we follow protobuf-go's
71
+ * parseTypeName: the prefix may contain URL characters, `/` separators, and
72
+ * well-formed percent-escapes, and the type name after the last `/` must be a
73
+ * dotted identifier.
74
+ */
75
+ readTypeName(): string;
76
+ private scan;
77
+ private skipSpace;
78
+ private scanIdentifier;
79
+ /**
80
+ * Scan a numeric literal. The sign is a separate token, so a number never
81
+ * starts with `-`. The literal must end at a delimiter, so `10f` is a float
82
+ * but `10bar`, `1.2.3`, `09`, and `0xZ` are errors.
83
+ */
84
+ private scanNumber;
85
+ private expectDelimiter;
86
+ private scanString;
87
+ private scanEscape;
88
+ private takeWhile;
89
+ private charAt;
90
+ }
91
+ export {};
@@ -0,0 +1,477 @@
1
+ // Copyright 2021-2026 Buf Technologies, Inc.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { getTextEncoding } from "../wire/text-encoding.js";
15
+ const tokenEof = { type: "eof" };
16
+ /**
17
+ * A tokenizer for the protobuf text format.
18
+ *
19
+ * The parser drives it with `peek()` and `next()` (one-token lookahead) and,
20
+ * once it knows it is in a field-name position, asks for the contents of a
21
+ * bracketed name with `readTypeName()` — the `[...]` syntax for extensions and
22
+ * Any type URLs is ambiguous with the list syntax at the lexical level. The
23
+ * structure is modeled on the graphql-js lexer: a single scan position and a
24
+ * per-token reader that returns the decoded value.
25
+ */
26
+ export class Reader {
27
+ constructor(input) {
28
+ this.pos = 0;
29
+ // A leading byte-order mark is insignificant; skip it like protobuf-go's
30
+ // tokenizer does.
31
+ this.input = input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
32
+ this.length = this.input.length;
33
+ }
34
+ /**
35
+ * Return the next token without consuming it.
36
+ */
37
+ peek() {
38
+ if (this.lookahead === undefined) {
39
+ this.lookahead = this.scan();
40
+ }
41
+ return this.lookahead;
42
+ }
43
+ /**
44
+ * Consume and return the next token.
45
+ */
46
+ next() {
47
+ const tok = this.peek();
48
+ this.lookahead = undefined;
49
+ return tok;
50
+ }
51
+ /**
52
+ * Read the contents of a bracketed name, used for extension fields and the
53
+ * expanded form of google.protobuf.Any. The opening `[` must already have
54
+ * been consumed with `next()`. Whitespace and comments inside the brackets
55
+ * are insignificant. Returns the inner name with the brackets removed, e.g.
56
+ * "pkg.Message.field" or "type.googleapis.com/pkg.Message".
57
+ *
58
+ * The text format grammar for this is incomplete, so we follow protobuf-go's
59
+ * parseTypeName: the prefix may contain URL characters, `/` separators, and
60
+ * well-formed percent-escapes, and the type name after the last `/` must be a
61
+ * dotted identifier.
62
+ */
63
+ readTypeName() {
64
+ let name = "";
65
+ for (;;) {
66
+ this.skipSpace();
67
+ const c = this.charAt(this.pos);
68
+ if (c === undefined) {
69
+ throw new Error("unterminated [...] name");
70
+ }
71
+ if (c === "]") {
72
+ this.pos++;
73
+ break;
74
+ }
75
+ if (c === "/") {
76
+ name += "/";
77
+ this.pos++;
78
+ }
79
+ else if (c === "%") {
80
+ if (!isHexDigit(this.charAt(this.pos + 1)) ||
81
+ !isHexDigit(this.charAt(this.pos + 2))) {
82
+ throw new Error("invalid percent-escape in [...] name");
83
+ }
84
+ name += this.input.substring(this.pos, this.pos + 3);
85
+ this.pos += 3;
86
+ }
87
+ else if (isUrlChar(c)) {
88
+ name += c;
89
+ this.pos++;
90
+ }
91
+ else {
92
+ throw new Error(`unexpected ${quoteChar(c)} in [...] name`);
93
+ }
94
+ }
95
+ validateTypeName(name);
96
+ return name;
97
+ }
98
+ scan() {
99
+ this.skipSpace();
100
+ const c = this.charAt(this.pos);
101
+ if (c === undefined) {
102
+ return tokenEof;
103
+ }
104
+ switch (c) {
105
+ case "{":
106
+ case "}":
107
+ case "<":
108
+ case ">":
109
+ case "[":
110
+ case "]":
111
+ case ":":
112
+ case ",":
113
+ case ";":
114
+ this.pos++;
115
+ return { type: c };
116
+ case "-":
117
+ // A "-" glued to a letter begins a negative identifier (-inf or
118
+ // -infinity); otherwise it is a sign token. A number may have whitespace
119
+ // between the sign and the digits, so the sign is a separate token the
120
+ // parser reassembles; a float literal may not, matching protobuf-go,
121
+ // where inf/infinity parse through the identifier path with the sign
122
+ // glued (so "- inf" is an error but "- 42" is -42).
123
+ if (isLetter(this.charAt(this.pos + 1))) {
124
+ return this.scanIdentifier();
125
+ }
126
+ this.pos++;
127
+ return { type: "-" };
128
+ case '"':
129
+ case "'":
130
+ return this.scanString(c);
131
+ }
132
+ if (isDigit(c)) {
133
+ return this.scanNumber();
134
+ }
135
+ if (c === "." && isDigit(this.charAt(this.pos + 1))) {
136
+ return this.scanNumber();
137
+ }
138
+ if (isLetter(c)) {
139
+ return this.scanIdentifier();
140
+ }
141
+ throw new Error(`unexpected ${quoteChar(c)}`);
142
+ }
143
+ skipSpace() {
144
+ for (;;) {
145
+ const c = this.input[this.pos];
146
+ if (c === " " ||
147
+ c === "\t" ||
148
+ c === "\n" ||
149
+ c === "\r" ||
150
+ c === "\v" ||
151
+ c === "\f") {
152
+ this.pos++;
153
+ }
154
+ else if (c === "#") {
155
+ // A comment runs to the end of the line.
156
+ this.pos++;
157
+ while (this.pos < this.length && this.input[this.pos] !== "\n") {
158
+ this.pos++;
159
+ }
160
+ }
161
+ else {
162
+ return;
163
+ }
164
+ }
165
+ }
166
+ scanIdentifier() {
167
+ const start = this.pos;
168
+ if (this.charAt(this.pos) === "-") {
169
+ this.pos++; // a glued negative identifier such as -inf
170
+ }
171
+ this.pos++; // the first letter (the caller guarantees one is present)
172
+ while (isLetterOrDigit(this.charAt(this.pos))) {
173
+ this.pos++;
174
+ }
175
+ return { type: "identifier", value: this.input.substring(start, this.pos) };
176
+ }
177
+ /**
178
+ * Scan a numeric literal. The sign is a separate token, so a number never
179
+ * starts with `-`. The literal must end at a delimiter, so `10f` is a float
180
+ * but `10bar`, `1.2.3`, `09`, and `0xZ` are errors.
181
+ */
182
+ scanNumber() {
183
+ var _a, _b, _c, _d;
184
+ const start = this.pos;
185
+ if (this.input[this.pos] === "0" &&
186
+ /[xX]/.test((_a = this.charAt(this.pos + 1)) !== null && _a !== void 0 ? _a : "")) {
187
+ // Hexadecimal: `0x` followed by one or more hex digits.
188
+ this.pos += 2;
189
+ const digits = this.pos;
190
+ while (isHexDigit(this.charAt(this.pos))) {
191
+ this.pos++;
192
+ }
193
+ if (this.pos === digits) {
194
+ throw new Error("invalid hexadecimal literal");
195
+ }
196
+ this.expectDelimiter();
197
+ return {
198
+ type: "int",
199
+ text: this.input.substring(start, this.pos),
200
+ base: 16,
201
+ };
202
+ }
203
+ if (this.input[this.pos] === "0" &&
204
+ isOctalDigit(this.charAt(this.pos + 1))) {
205
+ // Octal: a leading `0` followed by octal digits. A subsequent non-octal
206
+ // digit (as in `078`) ends the run, and the delimiter check rejects it.
207
+ this.pos++;
208
+ while (isOctalDigit(this.charAt(this.pos))) {
209
+ this.pos++;
210
+ }
211
+ this.expectDelimiter();
212
+ return {
213
+ type: "int",
214
+ text: this.input.substring(start, this.pos),
215
+ base: 8,
216
+ };
217
+ }
218
+ // A decimal integer or a floating point literal. A leading "0" stands
219
+ // alone (octal and hex were handled above), so the delimiter check below
220
+ // rejects a following digit — `08` and `09` are malformed, not decimal.
221
+ let isFloat = false;
222
+ if (this.charAt(this.pos) === "0") {
223
+ this.pos++;
224
+ }
225
+ else {
226
+ while (isDigit(this.charAt(this.pos))) {
227
+ this.pos++;
228
+ }
229
+ }
230
+ if (this.charAt(this.pos) === ".") {
231
+ isFloat = true;
232
+ this.pos++;
233
+ while (isDigit(this.charAt(this.pos))) {
234
+ this.pos++;
235
+ }
236
+ }
237
+ if (/[eE]/.test((_b = this.charAt(this.pos)) !== null && _b !== void 0 ? _b : "")) {
238
+ isFloat = true;
239
+ this.pos++;
240
+ if (/[+-]/.test((_c = this.charAt(this.pos)) !== null && _c !== void 0 ? _c : "")) {
241
+ this.pos++;
242
+ }
243
+ const digits = this.pos;
244
+ while (isDigit(this.charAt(this.pos))) {
245
+ this.pos++;
246
+ }
247
+ if (this.pos === digits) {
248
+ throw new Error("invalid exponent");
249
+ }
250
+ }
251
+ // A trailing `f`/`F` marks a float and is not part of the value passed to
252
+ // Number(); capture the end before consuming it so it stays out of the text.
253
+ const end = this.pos;
254
+ if (/[fF]/.test((_d = this.charAt(this.pos)) !== null && _d !== void 0 ? _d : "")) {
255
+ isFloat = true;
256
+ this.pos++;
257
+ }
258
+ this.expectDelimiter();
259
+ const text = this.input.substring(start, end);
260
+ return isFloat ? { type: "float", text } : { type: "int", text, base: 10 };
261
+ }
262
+ // A number must be terminated by a delimiter — any character that cannot
263
+ // continue a name or number. This rejects `09`, `0xZ`, `1.2.3`, and `5bar`.
264
+ expectDelimiter() {
265
+ const c = this.charAt(this.pos);
266
+ if (c !== undefined &&
267
+ (isLetterOrDigit(c) || c === "-" || c === "+" || c === ".")) {
268
+ throw new Error("invalid number");
269
+ }
270
+ }
271
+ scanString(quote) {
272
+ this.pos++; // opening quote
273
+ const bytes = [];
274
+ // Literal characters are accumulated as a run and encoded as UTF-8 in one
275
+ // batch when the run ends (at an escape or the closing quote). Escapes
276
+ // contribute their bytes directly.
277
+ let runStart = this.pos;
278
+ for (;;) {
279
+ const c = this.charAt(this.pos);
280
+ if (c === undefined) {
281
+ throw new Error("unterminated string");
282
+ }
283
+ if (c === quote) {
284
+ pushUtf8(bytes, this.input.substring(runStart, this.pos));
285
+ this.pos++; // closing quote
286
+ return { type: "string", value: new Uint8Array(bytes) };
287
+ }
288
+ if (c === "\\") {
289
+ pushUtf8(bytes, this.input.substring(runStart, this.pos));
290
+ this.pos++; // backslash
291
+ this.scanEscape(bytes);
292
+ runStart = this.pos;
293
+ continue;
294
+ }
295
+ // A raw newline or NUL is not allowed in a string, matching protobuf-go.
296
+ if (c === "\n" || c === "\0") {
297
+ throw new Error(`invalid ${quoteChar(c)} in string`);
298
+ }
299
+ this.pos++;
300
+ }
301
+ }
302
+ scanEscape(bytes) {
303
+ const c = this.charAt(this.pos);
304
+ if (c === undefined) {
305
+ throw new Error("unterminated escape sequence");
306
+ }
307
+ switch (c) {
308
+ case '"':
309
+ case "'":
310
+ case "\\":
311
+ case "?":
312
+ bytes.push(c.charCodeAt(0));
313
+ this.pos++;
314
+ return;
315
+ case "a":
316
+ bytes.push(0x07);
317
+ this.pos++;
318
+ return;
319
+ case "b":
320
+ bytes.push(0x08);
321
+ this.pos++;
322
+ return;
323
+ case "f":
324
+ bytes.push(0x0c);
325
+ this.pos++;
326
+ return;
327
+ case "n":
328
+ bytes.push(0x0a);
329
+ this.pos++;
330
+ return;
331
+ case "r":
332
+ bytes.push(0x0d);
333
+ this.pos++;
334
+ return;
335
+ case "t":
336
+ bytes.push(0x09);
337
+ this.pos++;
338
+ return;
339
+ case "v":
340
+ bytes.push(0x0b);
341
+ this.pos++;
342
+ return;
343
+ case "x": {
344
+ this.pos++;
345
+ const hex = this.takeWhile(isHexDigit, 2);
346
+ if (hex.length === 0) {
347
+ throw new Error("invalid hex escape \\x");
348
+ }
349
+ bytes.push(parseInt(hex, 16));
350
+ return;
351
+ }
352
+ case "u":
353
+ case "U": {
354
+ this.pos++;
355
+ const width = c === "u" ? 4 : 8;
356
+ const hex = this.takeWhile(isHexDigit, width);
357
+ if (hex.length !== width) {
358
+ throw new Error(`invalid unicode escape \\${c}`);
359
+ }
360
+ const code = parseInt(hex, 16);
361
+ // Reject surrogate code points and values beyond U+10FFFF. We
362
+ // deliberately do NOT combine an adjacent `\u` low surrogate into a
363
+ // pair the way protobuf-go does: the conformance suite (the
364
+ // StringLiteral*Surrogate* cases) requires every surrogate escape, lone
365
+ // or paired, to be a parse error. Do not "fix" this toward Go.
366
+ if (code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
367
+ throw new Error(`invalid unicode escape \\${c}${hex}`);
368
+ }
369
+ pushUtf8(bytes, String.fromCodePoint(code));
370
+ return;
371
+ }
372
+ default:
373
+ if (isOctalDigit(c)) {
374
+ const oct = this.takeWhile(isOctalDigit, 3);
375
+ const value = parseInt(oct, 8);
376
+ if (value > 0xff) {
377
+ throw new Error(`octal escape \\${oct} out of range`);
378
+ }
379
+ bytes.push(value);
380
+ return;
381
+ }
382
+ throw new Error(`invalid escape \\${c}`);
383
+ }
384
+ }
385
+ // Consume up to `max` consecutive characters matching `pred` and return them.
386
+ takeWhile(pred, max) {
387
+ const start = this.pos;
388
+ while (this.pos < start + max && pred(this.charAt(this.pos))) {
389
+ this.pos++;
390
+ }
391
+ return this.input.substring(start, this.pos);
392
+ }
393
+ charAt(index) {
394
+ return index < this.length ? this.input[index] : undefined;
395
+ }
396
+ }
397
+ /**
398
+ * Validate a type name (and URL prefix) from a bracketed name, mirroring
399
+ * protobuf-go's parseTypeName. The type name is everything after the last `/`;
400
+ * the prefix before it is a URL that may carry extra characters and
401
+ * percent-escapes, but must not begin with `/`.
402
+ */
403
+ function validateTypeName(name) {
404
+ const lastSlash = name.lastIndexOf("/");
405
+ if (lastSlash >= 0 && name[0] === "/") {
406
+ throw new Error("invalid type name: empty URL host");
407
+ }
408
+ const typeName = name.substring(lastSlash + 1);
409
+ if (typeName.length === 0) {
410
+ throw new Error("invalid type name: empty");
411
+ }
412
+ for (const part of typeName.split(".")) {
413
+ if (part.length === 0) {
414
+ throw new Error("invalid type name: empty component");
415
+ }
416
+ }
417
+ for (const c of typeName) {
418
+ if (!(isLetterOrDigit(c) || c === "." || c === "-")) {
419
+ throw new Error(`unexpected ${quoteChar(c)} in type name`);
420
+ }
421
+ }
422
+ }
423
+ function isDigit(c) {
424
+ return c !== undefined && c >= "0" && c <= "9";
425
+ }
426
+ function isOctalDigit(c) {
427
+ return c !== undefined && c >= "0" && c <= "7";
428
+ }
429
+ function isHexDigit(c) {
430
+ return (c !== undefined &&
431
+ ((c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F")));
432
+ }
433
+ function isLetter(c) {
434
+ return (c !== undefined &&
435
+ ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_"));
436
+ }
437
+ function isLetterOrDigit(c) {
438
+ return isLetter(c) || isDigit(c);
439
+ }
440
+ /**
441
+ * A character permitted in the URL prefix of an Any type name, matching
442
+ * protobuf-go's isUrlChar plus the type-name characters.
443
+ */
444
+ function isUrlChar(c) {
445
+ return (isLetterOrDigit(c) ||
446
+ c === "-" ||
447
+ c === "." ||
448
+ c === "~" ||
449
+ c === "!" ||
450
+ c === "$" ||
451
+ c === "&" ||
452
+ c === "(" ||
453
+ c === ")" ||
454
+ c === "*" ||
455
+ c === "+" ||
456
+ c === "," ||
457
+ c === ";" ||
458
+ c === "=");
459
+ }
460
+ function quoteChar(c) {
461
+ var _a;
462
+ const code = (_a = c.codePointAt(0)) !== null && _a !== void 0 ? _a : 0;
463
+ return code >= 0x20 && code <= 0x7e
464
+ ? `"${c}"`
465
+ : `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
466
+ }
467
+ // Append the UTF-8 encoding of `text` to `out`, using the same Text Encoding
468
+ // API as the rest of the library. Unpaired surrogates become U+FFFD (the
469
+ // standard TextEncoder behavior), matching binary serialization.
470
+ function pushUtf8(out, text) {
471
+ if (text.length === 0) {
472
+ return;
473
+ }
474
+ for (const byte of getTextEncoding().encodeUtf8(text)) {
475
+ out.push(byte);
476
+ }
477
+ }
@@ -0,0 +1,34 @@
1
+ import { type DescMessage } from "../descriptors.js";
2
+ import type { Registry } from "../registry.js";
3
+ import type { MessageShape } from "../types.js";
4
+ /**
5
+ * Options for serializing to the protobuf text format.
6
+ *
7
+ * The text format represents 64-bit integral types with BigInt and has no
8
+ * string fall-back: toText throws immediately when BigInt is unavailable.
9
+ */
10
+ export interface TextWriteOptions {
11
+ /**
12
+ * Print unknown fields?
13
+ *
14
+ * Disabled by default. This is a debugging aid only: unknown fields are
15
+ * printed by field number, and fromText rejects fields named by number, so
16
+ * output that includes them cannot be parsed back.
17
+ */
18
+ printUnknownFields: boolean;
19
+ /**
20
+ * The registry to resolve `google.protobuf.Any` and extensions. Without it,
21
+ * an Any is written as its raw `type_url`/`value` fields and extensions are
22
+ * omitted.
23
+ */
24
+ registry?: Registry | undefined;
25
+ }
26
+ /**
27
+ * Serialize a message to the protobuf text format.
28
+ *
29
+ * The output matches the default formatting of txtpbfmt: two-space indentation,
30
+ * one field per line, and a trailing newline.
31
+ *
32
+ * Requires BigInt: throws immediately if the environment does not support it.
33
+ */
34
+ export declare function toText<Desc extends DescMessage>(schema: Desc, message: MessageShape<Desc>, options?: Partial<TextWriteOptions>): string;