@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,355 @@
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 { ScalarType, } from "../descriptors.js";
15
+ import { protoInt64 } from "../proto-int64.js";
16
+ import { reflect } from "../reflect/reflect.js";
17
+ import { createExtensionContainer, getExtension } from "../extensions.js";
18
+ import { BinaryReader, WireType } from "../wire/index.js";
19
+ import { anyUnpack } from "../wkt/index.js";
20
+ import { isGroupLike } from "./is-group-like.js";
21
+ import { quoteBytes, quoteString, Writer } from "./writer.js";
22
+ const textWriteDefaults = {
23
+ printUnknownFields: false,
24
+ };
25
+ function makeWriteOptions(options) {
26
+ return options ? Object.assign(Object.assign({}, textWriteDefaults), options) : textWriteDefaults;
27
+ }
28
+ // A bound on nested unknown-field rendering. The known-field tree is a finite,
29
+ // valid in-memory message and needs no limit, but printUnknownFields re-parses
30
+ // bytes and recurses (the length-delimited and group heuristics), so we cap
31
+ // that path as defense-in-depth.
32
+ const unknownFieldDepthLimit = 100;
33
+ /**
34
+ * Serialize a message to the protobuf text format.
35
+ *
36
+ * The output matches the default formatting of txtpbfmt: two-space indentation,
37
+ * one field per line, and a trailing newline.
38
+ *
39
+ * Requires BigInt: throws immediately if the environment does not support it.
40
+ */
41
+ export function toText(schema, message, options) {
42
+ if (!protoInt64.supported) {
43
+ throw new Error("the protobuf text format requires BigInt, which is unavailable in this environment");
44
+ }
45
+ const writer = new Writer();
46
+ writeMessage(writer, reflect(schema, message), makeWriteOptions(options));
47
+ return writer.toString();
48
+ }
49
+ /**
50
+ * Write the body of a message: regular fields in declaration order, then
51
+ * resolvable extensions sorted by full name, then unknown fields by number
52
+ * (only when printUnknownFields is enabled). For `google.protobuf.Any`, the
53
+ * expanded form replaces all of this.
54
+ */
55
+ function writeMessage(writer, msg, opts) {
56
+ var _a;
57
+ if (writeAny(writer, msg, opts)) {
58
+ return;
59
+ }
60
+ for (const field of msg.fields) {
61
+ // Unset fields are omitted, including unset required fields; like
62
+ // protobuf-go, we do not validate required fields when serializing.
63
+ if (msg.isSet(field)) {
64
+ writeField(writer, fieldTextName(field), field, msg, opts);
65
+ }
66
+ }
67
+ const extensionNumbers = writeExtensions(writer, msg, opts);
68
+ if (opts.printUnknownFields) {
69
+ for (const field of (_a = msg.getUnknown()) !== null && _a !== void 0 ? _a : []) {
70
+ if (!extensionNumbers.has(field.no)) {
71
+ writeUnknownField(writer, field, 0);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ function writeField(writer, name, field, msg, opts) {
77
+ // Narrowing on fieldKind lets msg.get(field) return the precise reflect type
78
+ // for each case — ReflectMessage, ReflectList, ReflectMap, number, or a scalar
79
+ // value — so none of the branches need a cast.
80
+ switch (field.fieldKind) {
81
+ case "scalar":
82
+ writer.scalar(name, scalarToText(field.scalar, msg.get(field)));
83
+ break;
84
+ case "enum":
85
+ writer.scalar(name, enumToText(field.enum, msg.get(field)));
86
+ break;
87
+ case "message":
88
+ writeMessageValue(writer, name, msg.get(field), opts);
89
+ break;
90
+ case "list":
91
+ writeList(writer, name, field, msg.get(field), opts);
92
+ break;
93
+ case "map":
94
+ writeMap(writer, name, field, msg.get(field), opts);
95
+ break;
96
+ }
97
+ }
98
+ /**
99
+ * Write a message value as `name: { ... }`, or `name: {}` when it has no body.
100
+ * The body is rendered speculatively and rolled back if it turns out empty.
101
+ */
102
+ function writeMessageValue(writer, name, msg, opts) {
103
+ const mark = writer.mark();
104
+ writer.openMessage(name);
105
+ writeMessage(writer, msg, opts);
106
+ if (writer.writesSince(mark) === 1) {
107
+ // Only the opener was written, so the message is empty.
108
+ writer.reset(mark);
109
+ writer.emptyMessage(name);
110
+ }
111
+ else {
112
+ writer.end();
113
+ }
114
+ }
115
+ function writeList(writer, name, field, list, opts) {
116
+ switch (field.listKind) {
117
+ case "scalar":
118
+ for (const item of list) {
119
+ writer.scalar(name, scalarToText(field.scalar, item));
120
+ }
121
+ break;
122
+ case "enum":
123
+ for (const item of list) {
124
+ writer.scalar(name, enumToText(field.enum, item));
125
+ }
126
+ break;
127
+ case "message":
128
+ for (const item of list) {
129
+ writeMessageValue(writer, name, item, opts);
130
+ }
131
+ break;
132
+ }
133
+ }
134
+ function writeMap(writer, name, field, map, opts) {
135
+ // Map entries are emitted in iteration (insertion) order; unlike protobuf-go,
136
+ // we deliberately do not sort them.
137
+ for (const [key, value] of map) {
138
+ writer.openMessage(name);
139
+ writer.scalar("key", scalarToText(field.mapKey, key));
140
+ switch (field.mapKind) {
141
+ case "scalar":
142
+ writer.scalar("value", scalarToText(field.scalar, value));
143
+ break;
144
+ case "enum":
145
+ writer.scalar("value", enumToText(field.enum, value));
146
+ break;
147
+ case "message":
148
+ writeMessageValue(writer, "value", value, opts);
149
+ break;
150
+ }
151
+ writer.end();
152
+ }
153
+ }
154
+ /**
155
+ * Write `google.protobuf.Any` in its expanded form `[type.url]: { ... }`.
156
+ * Returns false (so the generic path writes `type_url`/`value` instead) when
157
+ * the message is not an Any, has no type URL, or the type cannot be resolved.
158
+ */
159
+ function writeAny(writer, msg, opts) {
160
+ if (msg.desc.typeName !== "google.protobuf.Any" ||
161
+ opts.registry === undefined) {
162
+ return false;
163
+ }
164
+ const any = msg.message;
165
+ if (any.typeUrl === "") {
166
+ return false;
167
+ }
168
+ const unpacked = anyUnpack(any, opts.registry);
169
+ if (unpacked === undefined) {
170
+ return false;
171
+ }
172
+ const desc = opts.registry.getMessage(unpacked.$typeName);
173
+ if (desc === undefined) {
174
+ return false;
175
+ }
176
+ // The bracketed name preserves the exact type URL, including a custom domain.
177
+ writeMessageValue(writer, "[" + any.typeUrl + "]", reflect(desc, unpacked), opts);
178
+ return true;
179
+ }
180
+ /**
181
+ * Write resolvable extensions, sorted by full name, and return their field
182
+ * numbers so writeMessage does not also emit them as raw unknown fields.
183
+ */
184
+ function writeExtensions(writer, msg, opts) {
185
+ const numbers = new Set();
186
+ const unknown = msg.getUnknown();
187
+ if (opts.registry === undefined || unknown === undefined) {
188
+ return numbers;
189
+ }
190
+ const extensions = [];
191
+ for (const { no } of unknown) {
192
+ if (numbers.has(no)) {
193
+ continue;
194
+ }
195
+ const extension = opts.registry.getExtensionFor(msg.desc, no);
196
+ if (extension !== undefined) {
197
+ numbers.add(no);
198
+ extensions.push(extension);
199
+ }
200
+ }
201
+ extensions.sort((a, b) => a.typeName < b.typeName ? -1 : a.typeName > b.typeName ? 1 : 0);
202
+ for (const extension of extensions) {
203
+ const value = getExtension(msg.message, extension);
204
+ const [container, field] = createExtensionContainer(extension, value);
205
+ writeField(writer, "[" + extension.typeName + "]", field, container, opts);
206
+ }
207
+ return numbers;
208
+ }
209
+ /**
210
+ * Write an unknown field by its field number, mirroring protobuf-go: varints as
211
+ * decimal, fixed-width values as hexadecimal, length-delimited data as a nested
212
+ * message when it parses cleanly as one and a quoted byte string otherwise, and
213
+ * groups recursively.
214
+ */
215
+ function writeUnknownField(writer, field, depth) {
216
+ const name = field.no.toString();
217
+ const reader = new BinaryReader(field.data);
218
+ switch (field.wireType) {
219
+ case WireType.Varint:
220
+ writer.scalar(name, reader.uint64().toString());
221
+ break;
222
+ case WireType.Bit32:
223
+ writer.scalar(name, "0x" + (reader.fixed32() >>> 0).toString(16).padStart(8, "0"));
224
+ break;
225
+ case WireType.Bit64:
226
+ writer.scalar(name, "0x" + BigInt(reader.fixed64()).toString(16).padStart(16, "0"));
227
+ break;
228
+ case WireType.LengthDelimited: {
229
+ const bytes = reader.bytes();
230
+ const nested = depth < unknownFieldDepthLimit ? parseUnknownMessage(bytes) : undefined;
231
+ if (nested === undefined) {
232
+ writer.scalar(name, quoteBytes(bytes));
233
+ }
234
+ else {
235
+ writeUnknownGroup(writer, name, nested, depth);
236
+ }
237
+ break;
238
+ }
239
+ case WireType.StartGroup: {
240
+ const fields = [];
241
+ while (reader.pos < reader.len) {
242
+ const [no, wireType] = reader.tag();
243
+ if (wireType === WireType.EndGroup) {
244
+ break;
245
+ }
246
+ fields.push({ no, wireType, data: reader.skip(wireType, no) });
247
+ }
248
+ writeUnknownGroup(writer, name, fields, depth);
249
+ break;
250
+ }
251
+ }
252
+ }
253
+ function writeUnknownGroup(writer, name, fields, depth) {
254
+ if (fields.length === 0) {
255
+ writer.emptyMessage(name);
256
+ return;
257
+ }
258
+ writer.openMessage(name);
259
+ for (const field of fields) {
260
+ writeUnknownField(writer, field, depth + 1);
261
+ }
262
+ writer.end();
263
+ }
264
+ /**
265
+ * Try to interpret length-delimited bytes as a nested message. Returns its
266
+ * unknown fields if the bytes parse cleanly and completely, otherwise undefined
267
+ * (in which case the data is rendered as a quoted byte string).
268
+ */
269
+ function parseUnknownMessage(bytes) {
270
+ if (bytes.length === 0) {
271
+ return undefined;
272
+ }
273
+ const reader = new BinaryReader(bytes);
274
+ const fields = [];
275
+ try {
276
+ while (reader.pos < reader.len) {
277
+ const [no, wireType] = reader.tag();
278
+ if (no <= 0 || wireType === WireType.EndGroup) {
279
+ return undefined;
280
+ }
281
+ fields.push({ no, wireType, data: reader.skip(wireType, no) });
282
+ }
283
+ }
284
+ catch (_a) {
285
+ return undefined;
286
+ }
287
+ return reader.pos === reader.len ? fields : undefined;
288
+ }
289
+ /**
290
+ * The name a field is addressed by in the text format: a group-like (delimited)
291
+ * field uses its message type name, every other field its proto name.
292
+ */
293
+ function fieldTextName(field) {
294
+ return isGroupLike(field) ? field.message.name : field.name;
295
+ }
296
+ function scalarToText(type, value) {
297
+ switch (type) {
298
+ case ScalarType.STRING:
299
+ return quoteString(value);
300
+ case ScalarType.BYTES:
301
+ return quoteBytes(value);
302
+ case ScalarType.BOOL:
303
+ return value === true ? "true" : "false";
304
+ case ScalarType.FLOAT:
305
+ return floatToText(value, true);
306
+ case ScalarType.DOUBLE:
307
+ return floatToText(value, false);
308
+ default:
309
+ // All integer types print as decimal with no prefix. 64-bit values are
310
+ // bigint; String() gives the decimal form for both bigint and number.
311
+ return String(value);
312
+ }
313
+ }
314
+ function enumToText(descEnum, value) {
315
+ // Emit the first-declared name for a value, so allow_alias enums match
316
+ // protobuf-go (the by-number record can resolve to a non-first alias). An
317
+ // unknown value prints as a decimal.
318
+ for (const v of descEnum.values) {
319
+ if (v.number === value) {
320
+ return v.name;
321
+ }
322
+ }
323
+ return value.toString();
324
+ }
325
+ function floatToText(value, single) {
326
+ // Round to 32-bit precision first so an overflow becomes inf (not the JS
327
+ // "Infinity") and the value is the true 32-bit value before we test it.
328
+ const n = single ? Math.fround(value) : value;
329
+ if (Number.isNaN(n)) {
330
+ return "nan";
331
+ }
332
+ if (n === Number.POSITIVE_INFINITY) {
333
+ return "inf";
334
+ }
335
+ if (n === Number.NEGATIVE_INFINITY) {
336
+ return "-inf";
337
+ }
338
+ if (Object.is(n, -0)) {
339
+ return "-0";
340
+ }
341
+ if (!single) {
342
+ // Number.prototype.toString already yields the shortest decimal that
343
+ // round-trips to the same 64-bit value.
344
+ return n.toString();
345
+ }
346
+ // For 32-bit floats, find the shortest decimal that round-trips to the same
347
+ // float32, mirroring strconv.AppendFloat(n, 'g', -1, 32) in protobuf-go.
348
+ for (let precision = 1; precision <= 9; precision++) {
349
+ const candidate = Number(n.toPrecision(precision));
350
+ if (Math.fround(candidate) === n) {
351
+ return candidate.toString();
352
+ }
353
+ }
354
+ return n.toString();
355
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * A position in the Writer's output, used to roll back speculative writes.
3
+ */
4
+ interface Mark {
5
+ readonly size: number;
6
+ readonly depth: number;
7
+ }
8
+ /**
9
+ * A writer for the protobuf text format.
10
+ *
11
+ * The Writer owns layout: indentation, line breaks, and braces. Its output
12
+ * matches the default formatting of txtpbfmt and the multi-line output of
13
+ * protobuf-go: two-space indentation, `name: value` with a single space after
14
+ * the colon, submessages as `name: {` with the body indented and `}` aligned
15
+ * under the field name, and a trailing newline. It never inserts the randomized
16
+ * extra spaces that protobuf-go adds to discourage parsing its output as
17
+ * canonical.
18
+ *
19
+ * Output accumulates into a single buffer of lines, so a deep tree costs no
20
+ * more than the bytes it prints. To decide between `name: {}` and an indented
21
+ * block, the caller renders the body, then rolls back with mark()/reset() if it
22
+ * turned out empty — an O(1) decision that needs no separate child buffer.
23
+ */
24
+ export declare class Writer {
25
+ private readonly chunks;
26
+ private depth;
27
+ /**
28
+ * Write a scalar field: `<indent>name: value` followed by a newline.
29
+ */
30
+ scalar(name: string, value: string): void;
31
+ /**
32
+ * Write an empty message field: `<indent>name: {}` followed by a newline.
33
+ */
34
+ emptyMessage(name: string): void;
35
+ /**
36
+ * Open a message field: `<indent>name: {` followed by a newline, then indent
37
+ * the body. Close it with end().
38
+ */
39
+ openMessage(name: string): void;
40
+ /**
41
+ * Close a message opened with openMessage(): outdent and write `<indent>}`
42
+ * followed by a newline.
43
+ */
44
+ end(): void;
45
+ /**
46
+ * Capture the current output position so it can be rolled back with reset().
47
+ */
48
+ mark(): Mark;
49
+ /**
50
+ * Roll back to a position captured with mark().
51
+ */
52
+ reset(mark: Mark): void;
53
+ /**
54
+ * The number of lines written since the given mark.
55
+ */
56
+ writesSince(mark: Mark): number;
57
+ toString(): string;
58
+ private indent;
59
+ }
60
+ /**
61
+ * Quote and escape a string field value as a double-quoted text format literal.
62
+ *
63
+ * Uses the single escaping decision in escapeCodePoint, so string fields, bytes
64
+ * fields, and unknown length-delimited rendering can never drift apart. Valid
65
+ * non-ASCII passes through as raw UTF-8; surrogates are never escaped.
66
+ */
67
+ export declare function quoteString(value: string): string;
68
+ /**
69
+ * Quote and escape a bytes field value as a double-quoted text format literal.
70
+ *
71
+ * Valid UTF-8 runs are emitted with escapeCodePoint (so they read identically
72
+ * to a string field); any byte that is not part of a valid UTF-8 sequence is
73
+ * emitted as `\xHH`, keeping the output plain ASCII that round-trips exactly.
74
+ */
75
+ export declare function quoteBytes(value: Uint8Array): string;
76
+ export {};
@@ -0,0 +1,214 @@
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
+ const indentUnit = " ";
15
+ /**
16
+ * A writer for the protobuf text format.
17
+ *
18
+ * The Writer owns layout: indentation, line breaks, and braces. Its output
19
+ * matches the default formatting of txtpbfmt and the multi-line output of
20
+ * protobuf-go: two-space indentation, `name: value` with a single space after
21
+ * the colon, submessages as `name: {` with the body indented and `}` aligned
22
+ * under the field name, and a trailing newline. It never inserts the randomized
23
+ * extra spaces that protobuf-go adds to discourage parsing its output as
24
+ * canonical.
25
+ *
26
+ * Output accumulates into a single buffer of lines, so a deep tree costs no
27
+ * more than the bytes it prints. To decide between `name: {}` and an indented
28
+ * block, the caller renders the body, then rolls back with mark()/reset() if it
29
+ * turned out empty — an O(1) decision that needs no separate child buffer.
30
+ */
31
+ export class Writer {
32
+ constructor() {
33
+ this.chunks = [];
34
+ this.depth = 0;
35
+ }
36
+ /**
37
+ * Write a scalar field: `<indent>name: value` followed by a newline.
38
+ */
39
+ scalar(name, value) {
40
+ this.chunks.push(this.indent() + name + ": " + value + "\n");
41
+ }
42
+ /**
43
+ * Write an empty message field: `<indent>name: {}` followed by a newline.
44
+ */
45
+ emptyMessage(name) {
46
+ this.chunks.push(this.indent() + name + ": {}\n");
47
+ }
48
+ /**
49
+ * Open a message field: `<indent>name: {` followed by a newline, then indent
50
+ * the body. Close it with end().
51
+ */
52
+ openMessage(name) {
53
+ this.chunks.push(this.indent() + name + ": {\n");
54
+ this.depth++;
55
+ }
56
+ /**
57
+ * Close a message opened with openMessage(): outdent and write `<indent>}`
58
+ * followed by a newline.
59
+ */
60
+ end() {
61
+ this.depth--;
62
+ this.chunks.push(this.indent() + "}\n");
63
+ }
64
+ /**
65
+ * Capture the current output position so it can be rolled back with reset().
66
+ */
67
+ mark() {
68
+ return { size: this.chunks.length, depth: this.depth };
69
+ }
70
+ /**
71
+ * Roll back to a position captured with mark().
72
+ */
73
+ reset(mark) {
74
+ this.chunks.length = mark.size;
75
+ this.depth = mark.depth;
76
+ }
77
+ /**
78
+ * The number of lines written since the given mark.
79
+ */
80
+ writesSince(mark) {
81
+ return this.chunks.length - mark.size;
82
+ }
83
+ toString() {
84
+ return this.chunks.join("");
85
+ }
86
+ indent() {
87
+ return indentUnit.repeat(this.depth);
88
+ }
89
+ }
90
+ /**
91
+ * Quote and escape a string field value as a double-quoted text format literal.
92
+ *
93
+ * Uses the single escaping decision in escapeCodePoint, so string fields, bytes
94
+ * fields, and unknown length-delimited rendering can never drift apart. Valid
95
+ * non-ASCII passes through as raw UTF-8; surrogates are never escaped.
96
+ */
97
+ export function quoteString(value) {
98
+ var _a;
99
+ let out = '"';
100
+ for (const ch of value) {
101
+ out += (_a = escapeCodePoint(ch.codePointAt(0))) !== null && _a !== void 0 ? _a : ch;
102
+ }
103
+ return out + '"';
104
+ }
105
+ /**
106
+ * Quote and escape a bytes field value as a double-quoted text format literal.
107
+ *
108
+ * Valid UTF-8 runs are emitted with escapeCodePoint (so they read identically
109
+ * to a string field); any byte that is not part of a valid UTF-8 sequence is
110
+ * emitted as `\xHH`, keeping the output plain ASCII that round-trips exactly.
111
+ */
112
+ export function quoteBytes(value) {
113
+ var _a;
114
+ let out = '"';
115
+ for (let i = 0; i < value.length;) {
116
+ const rune = decodeUtf8(value, i);
117
+ if (rune === undefined) {
118
+ out += "\\x" + hex2(value[i]);
119
+ i++;
120
+ continue;
121
+ }
122
+ out += (_a = escapeCodePoint(rune.code)) !== null && _a !== void 0 ? _a : String.fromCodePoint(rune.code);
123
+ i += rune.size;
124
+ }
125
+ return out + '"';
126
+ }
127
+ /**
128
+ * The single source of truth for escaping a code point in a text format string
129
+ * literal. Returns the escape sequence, or undefined when the code point may be
130
+ * emitted raw.
131
+ *
132
+ * Escapes the conventional sequences, all C0 controls and DEL as `\xHH`, and
133
+ * the C1 controls (U+0080–U+009F) as `\u00HH`. Surrogates and everything else
134
+ * pass through raw.
135
+ */
136
+ function escapeCodePoint(code) {
137
+ switch (code) {
138
+ case 0x5c:
139
+ return "\\\\";
140
+ case 0x22:
141
+ return '\\"';
142
+ case 0x0a:
143
+ return "\\n";
144
+ case 0x0d:
145
+ return "\\r";
146
+ case 0x09:
147
+ return "\\t";
148
+ }
149
+ if (code < 0x20 || code === 0x7f) {
150
+ return "\\x" + hex2(code);
151
+ }
152
+ if (code >= 0x80 && code <= 0x9f) {
153
+ return "\\u" + code.toString(16).padStart(4, "0");
154
+ }
155
+ return undefined;
156
+ }
157
+ /**
158
+ * Decode the UTF-8 sequence starting at `offset`, returning the code point and
159
+ * its byte length, or undefined if the bytes there are not valid UTF-8. We
160
+ * decode manually (rather than via TextDecoder) so an invalid byte can be
161
+ * pinpointed and escaped individually.
162
+ */
163
+ function decodeUtf8(bytes, offset) {
164
+ const b0 = bytes[offset];
165
+ if (b0 < 0x80) {
166
+ return { code: b0, size: 1 };
167
+ }
168
+ if (b0 < 0xc0) {
169
+ return undefined;
170
+ }
171
+ if (b0 < 0xe0) {
172
+ const b1 = bytes[offset + 1];
173
+ if (!isContinuation(b1)) {
174
+ return undefined;
175
+ }
176
+ const code = ((b0 & 0x1f) << 6) | (b1 & 0x3f);
177
+ return code < 0x80 ? undefined : { code, size: 2 };
178
+ }
179
+ if (b0 < 0xf0) {
180
+ const b1 = bytes[offset + 1];
181
+ const b2 = bytes[offset + 2];
182
+ if (!isContinuation(b1) || !isContinuation(b2)) {
183
+ return undefined;
184
+ }
185
+ const code = ((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f);
186
+ if (code < 0x800 || (code >= 0xd800 && code <= 0xdfff)) {
187
+ return undefined;
188
+ }
189
+ return { code, size: 3 };
190
+ }
191
+ if (b0 < 0xf8) {
192
+ const b1 = bytes[offset + 1];
193
+ const b2 = bytes[offset + 2];
194
+ const b3 = bytes[offset + 3];
195
+ if (!isContinuation(b1) || !isContinuation(b2) || !isContinuation(b3)) {
196
+ return undefined;
197
+ }
198
+ const code = ((b0 & 0x07) << 18) |
199
+ ((b1 & 0x3f) << 12) |
200
+ ((b2 & 0x3f) << 6) |
201
+ (b3 & 0x3f);
202
+ if (code < 0x10000 || code > 0x10ffff) {
203
+ return undefined;
204
+ }
205
+ return { code, size: 4 };
206
+ }
207
+ return undefined;
208
+ }
209
+ function isContinuation(byte) {
210
+ return byte !== undefined && (byte & 0xc0) === 0x80;
211
+ }
212
+ function hex2(value) {
213
+ return value.toString(16).padStart(2, "0");
214
+ }
@@ -71,6 +71,13 @@ export type UnknownField = {
71
71
  readonly wireType: WireType;
72
72
  readonly data: Uint8Array;
73
73
  };
74
+ /**
75
+ * An unknown enum is a value that's not in the set of values defined by an
76
+ * enum. Open Protobuf enums may
77
+ */
78
+ export type UnknownEnum = number & {
79
+ __unknown_enum: true;
80
+ };
74
81
  /**
75
82
  * Describes a streaming RPC declaration.
76
83
  */
@@ -0,0 +1,10 @@
1
+ import type { DescEnum } from "./descriptors.js";
2
+ import type { UnknownEnum } from "./types.js";
3
+ /**
4
+ * Open enums can contain numeric values that are not in the set of values
5
+ * defined by the enum.
6
+ *
7
+ * This function returns true for those values, and narrows the type to
8
+ * `UnknownEnum`.
9
+ */
10
+ export declare function isUnknownEnum(desc: DescEnum, value: number): value is UnknownEnum;
@@ -0,0 +1,23 @@
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
+ * Open enums can contain numeric values that are not in the set of values
16
+ * defined by the enum.
17
+ *
18
+ * This function returns true for those values, and narrows the type to
19
+ * `UnknownEnum`.
20
+ */
21
+ export function isUnknownEnum(desc, value) {
22
+ return desc.value[value] === undefined;
23
+ }