@zmdb/compiler 1.0.0-beta.1

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 (172) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +40 -0
  3. package/dist/codegen/index.d.ts +50 -0
  4. package/dist/codegen/index.d.ts.map +1 -0
  5. package/dist/codegen/index.js +364 -0
  6. package/dist/codegen/index.js.map +1 -0
  7. package/dist/codegen/scan.d.ts +105 -0
  8. package/dist/codegen/scan.d.ts.map +1 -0
  9. package/dist/codegen/scan.js +450 -0
  10. package/dist/codegen/scan.js.map +1 -0
  11. package/dist/codegen/witness.d.ts +76 -0
  12. package/dist/codegen/witness.d.ts.map +1 -0
  13. package/dist/codegen/witness.js +690 -0
  14. package/dist/codegen/witness.js.map +1 -0
  15. package/dist/config/contract.d.ts +41 -0
  16. package/dist/config/contract.d.ts.map +1 -0
  17. package/dist/config/contract.js +5 -0
  18. package/dist/config/contract.js.map +1 -0
  19. package/dist/config/index.d.ts +53 -0
  20. package/dist/config/index.d.ts.map +1 -0
  21. package/dist/config/index.js +277 -0
  22. package/dist/config/index.js.map +1 -0
  23. package/dist/config/index.zmdb.generated.d.ts +12 -0
  24. package/dist/config/index.zmdb.generated.js +28 -0
  25. package/dist/config/index.zmdb.witness.d.ts +3 -0
  26. package/dist/config/index.zmdb.witness.d.ts.map +1 -0
  27. package/dist/config/index.zmdb.witness.js +20 -0
  28. package/dist/config/index.zmdb.witness.js.map +1 -0
  29. package/dist/configured-plugin.d.ts +11 -0
  30. package/dist/configured-plugin.d.ts.map +1 -0
  31. package/dist/configured-plugin.js +27 -0
  32. package/dist/configured-plugin.js.map +1 -0
  33. package/dist/emit/index.d.ts +103 -0
  34. package/dist/emit/index.d.ts.map +1 -0
  35. package/dist/emit/index.js +1356 -0
  36. package/dist/emit/index.js.map +1 -0
  37. package/dist/errors.d.ts +7 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +2 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/index.d.ts +46 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +254 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/lint/ast.d.ts +17 -0
  46. package/dist/lint/ast.d.ts.map +1 -0
  47. package/dist/lint/ast.js +77 -0
  48. package/dist/lint/ast.js.map +1 -0
  49. package/dist/lint/host-types.d.ts +9 -0
  50. package/dist/lint/host-types.d.ts.map +1 -0
  51. package/dist/lint/host-types.js +2 -0
  52. package/dist/lint/host-types.js.map +1 -0
  53. package/dist/lint/index.d.ts +23 -0
  54. package/dist/lint/index.d.ts.map +1 -0
  55. package/dist/lint/index.js +53 -0
  56. package/dist/lint/index.js.map +1 -0
  57. package/dist/lint/rules/no-distributed-nullable-tags.d.ts +3 -0
  58. package/dist/lint/rules/no-distributed-nullable-tags.d.ts.map +1 -0
  59. package/dist/lint/rules/no-distributed-nullable-tags.js +71 -0
  60. package/dist/lint/rules/no-distributed-nullable-tags.js.map +1 -0
  61. package/dist/lint/rules/no-empty-patch.d.ts +3 -0
  62. package/dist/lint/rules/no-empty-patch.d.ts.map +1 -0
  63. package/dist/lint/rules/no-empty-patch.js +26 -0
  64. package/dist/lint/rules/no-empty-patch.js.map +1 -0
  65. package/dist/lint/rules/no-interpolated-sql.d.ts +3 -0
  66. package/dist/lint/rules/no-interpolated-sql.d.ts.map +1 -0
  67. package/dist/lint/rules/no-interpolated-sql.js +35 -0
  68. package/dist/lint/rules/no-interpolated-sql.js.map +1 -0
  69. package/dist/lint/rules/no-unbounded-find.d.ts +3 -0
  70. package/dist/lint/rules/no-unbounded-find.d.ts.map +1 -0
  71. package/dist/lint/rules/no-unbounded-find.js +26 -0
  72. package/dist/lint/rules/no-unbounded-find.js.map +1 -0
  73. package/dist/lint/rules/no-unknown-json-column.d.ts +3 -0
  74. package/dist/lint/rules/no-unknown-json-column.d.ts.map +1 -0
  75. package/dist/lint/rules/no-unknown-json-column.js +35 -0
  76. package/dist/lint/rules/no-unknown-json-column.js.map +1 -0
  77. package/dist/lint/rules/require-sql-on-number.d.ts +3 -0
  78. package/dist/lint/rules/require-sql-on-number.d.ts.map +1 -0
  79. package/dist/lint/rules/require-sql-on-number.js +34 -0
  80. package/dist/lint/rules/require-sql-on-number.js.map +1 -0
  81. package/dist/lint/types.d.ts +12 -0
  82. package/dist/lint/types.d.ts.map +1 -0
  83. package/dist/lint/types.js +2 -0
  84. package/dist/lint/types.js.map +1 -0
  85. package/dist/metro/metro.d.ts +22 -0
  86. package/dist/metro/metro.d.ts.map +1 -0
  87. package/dist/metro/metro.js +242 -0
  88. package/dist/metro/metro.js.map +1 -0
  89. package/dist/protobuf/decode.d.ts +20 -0
  90. package/dist/protobuf/decode.d.ts.map +1 -0
  91. package/dist/protobuf/decode.js +289 -0
  92. package/dist/protobuf/decode.js.map +1 -0
  93. package/dist/protobuf/descriptor.d.ts +16 -0
  94. package/dist/protobuf/descriptor.d.ts.map +1 -0
  95. package/dist/protobuf/descriptor.js +303 -0
  96. package/dist/protobuf/descriptor.js.map +1 -0
  97. package/dist/protobuf/encode.d.ts +20 -0
  98. package/dist/protobuf/encode.d.ts.map +1 -0
  99. package/dist/protobuf/encode.js +203 -0
  100. package/dist/protobuf/encode.js.map +1 -0
  101. package/dist/protobuf/grpc-ir.d.ts +16 -0
  102. package/dist/protobuf/grpc-ir.d.ts.map +1 -0
  103. package/dist/protobuf/grpc-ir.js +2 -0
  104. package/dist/protobuf/grpc-ir.js.map +1 -0
  105. package/dist/protobuf/plan.d.ts +42 -0
  106. package/dist/protobuf/plan.d.ts.map +1 -0
  107. package/dist/protobuf/plan.js +158 -0
  108. package/dist/protobuf/plan.js.map +1 -0
  109. package/dist/reflect/callsites.d.ts +40 -0
  110. package/dist/reflect/callsites.d.ts.map +1 -0
  111. package/dist/reflect/callsites.js +153 -0
  112. package/dist/reflect/callsites.js.map +1 -0
  113. package/dist/reflect/index.d.ts +106 -0
  114. package/dist/reflect/index.d.ts.map +1 -0
  115. package/dist/reflect/index.js +1421 -0
  116. package/dist/reflect/index.js.map +1 -0
  117. package/dist/reflect/session.d.ts +118 -0
  118. package/dist/reflect/session.d.ts.map +1 -0
  119. package/dist/reflect/session.js +228 -0
  120. package/dist/reflect/session.js.map +1 -0
  121. package/dist/testing/index.d.ts +69 -0
  122. package/dist/testing/index.d.ts.map +1 -0
  123. package/dist/testing/index.js +210 -0
  124. package/dist/testing/index.js.map +1 -0
  125. package/dist/transform/index.d.ts +74 -0
  126. package/dist/transform/index.d.ts.map +1 -0
  127. package/dist/transform/index.js +572 -0
  128. package/dist/transform/index.js.map +1 -0
  129. package/dist/unplugin/index.d.ts +53 -0
  130. package/dist/unplugin/index.d.ts.map +1 -0
  131. package/dist/unplugin/index.js +130 -0
  132. package/dist/unplugin/index.js.map +1 -0
  133. package/dist/unplugin/inline-bench.d.ts +29 -0
  134. package/dist/unplugin/inline-bench.d.ts.map +1 -0
  135. package/dist/unplugin/inline-bench.js +82 -0
  136. package/dist/unplugin/inline-bench.js.map +1 -0
  137. package/package.json +110 -0
  138. package/src/codegen/index.ts +451 -0
  139. package/src/codegen/scan.ts +574 -0
  140. package/src/codegen/witness.ts +819 -0
  141. package/src/config/contract.ts +48 -0
  142. package/src/config/index.ts +384 -0
  143. package/src/config/index.zmdb.generated.d.ts +12 -0
  144. package/src/config/index.zmdb.generated.js +28 -0
  145. package/src/config/index.zmdb.witness.ts +22 -0
  146. package/src/configured-plugin.ts +35 -0
  147. package/src/emit/index.ts +1496 -0
  148. package/src/errors.ts +6 -0
  149. package/src/index.ts +336 -0
  150. package/src/lint/ast.ts +96 -0
  151. package/src/lint/host-types.ts +9 -0
  152. package/src/lint/index.ts +80 -0
  153. package/src/lint/rules/no-distributed-nullable-tags.ts +76 -0
  154. package/src/lint/rules/no-empty-patch.ts +26 -0
  155. package/src/lint/rules/no-interpolated-sql.ts +45 -0
  156. package/src/lint/rules/no-unbounded-find.ts +26 -0
  157. package/src/lint/rules/no-unknown-json-column.ts +36 -0
  158. package/src/lint/rules/require-sql-on-number.ts +34 -0
  159. package/src/lint/types.ts +11 -0
  160. package/src/metro/metro.ts +313 -0
  161. package/src/protobuf/decode.ts +375 -0
  162. package/src/protobuf/descriptor.ts +353 -0
  163. package/src/protobuf/encode.ts +277 -0
  164. package/src/protobuf/grpc-ir.ts +17 -0
  165. package/src/protobuf/plan.ts +244 -0
  166. package/src/reflect/callsites.ts +198 -0
  167. package/src/reflect/index.ts +1723 -0
  168. package/src/reflect/session.ts +276 -0
  169. package/src/testing/index.ts +298 -0
  170. package/src/transform/index.ts +690 -0
  171. package/src/unplugin/index.ts +182 -0
  172. package/src/unplugin/inline-bench.ts +92 -0
@@ -0,0 +1,375 @@
1
+ // TypeIR -> field-number-dispatched protobuf decoder JavaScript.
2
+ //
3
+ // This is build-time only. Generated helpers construct the declared object shape and
4
+ // switch directly on known field numbers. The runtime reader owns byte bounds and wire
5
+ // primitives only; no descriptor or property table reaches the application.
6
+
7
+ import { type ObjectIR, type PropertyIR, type ScalarIR, type TypeIR } from '@zmdb/schema/ir';
8
+
9
+ import {
10
+ collectMessages,
11
+ IDENTIFIER,
12
+ isPacked,
13
+ planField,
14
+ planScalar,
15
+ safeName,
16
+ type AtomPlan as ProtoAtomPlan,
17
+ type EnumPlan,
18
+ type FieldAtomIR,
19
+ type FieldPlan as ProtoFieldPlan,
20
+ } from './plan.js';
21
+
22
+ export interface ProtoDecodeDiagnostic {
23
+ readonly path: string;
24
+ readonly reason: string;
25
+ readonly source?: string;
26
+ }
27
+
28
+ export interface ProtoDecoderOptions {
29
+ /** Unique valid-identifier prefix supplied by the file-level emitter. */
30
+ readonly namespace: string;
31
+ /** Local identifier bound to the emitted wire-runtime import. */
32
+ readonly reader: string;
33
+ }
34
+
35
+ export interface ProtoDecoderResult {
36
+ readonly entry?: string;
37
+ readonly helpers: readonly string[];
38
+ readonly diagnostics: readonly ProtoDecodeDiagnostic[];
39
+ }
40
+
41
+ type AtomPlan = ProtoAtomPlan<string>;
42
+ type FieldPlan = ProtoFieldPlan<AtomPlan>;
43
+
44
+ interface PlannedField {
45
+ readonly property: PropertyIR;
46
+ readonly path: string;
47
+ readonly plan: FieldPlan;
48
+ }
49
+
50
+ const SCALAR_ZERO = { number: '0', bigint: '0n', boolean: 'false', string: '""' } as const;
51
+
52
+ /** Emit all helper declarations and the root helper to call, or named diagnostics. */
53
+ export function emitProtoDecoder(
54
+ root: TypeIR,
55
+ preferredName: string,
56
+ options: ProtoDecoderOptions,
57
+ ): ProtoDecoderResult {
58
+ return new DecoderEmitter(options).emit(root, preferredName);
59
+ }
60
+
61
+ class DecoderEmitter {
62
+ readonly #options: ProtoDecoderOptions;
63
+ readonly #diagnostics: ProtoDecodeDiagnostic[] = [];
64
+ readonly #objects: ObjectIR[] = [];
65
+ readonly #objectHelpers = new Map<ObjectIR, string>();
66
+ readonly #namedObjects = new Map<string, ObjectIR>();
67
+ readonly #auxiliary: string[] = [];
68
+ readonly #messages: string[] = [];
69
+ #counter = 0;
70
+ #timestampHelper: string | undefined;
71
+
72
+ constructor(options: ProtoDecoderOptions) {
73
+ this.#options = options;
74
+ }
75
+
76
+ emit(root: TypeIR, preferredName: string): ProtoDecoderResult {
77
+ if (root.kind !== 'object') {
78
+ const reason =
79
+ root.kind === 'union' && root.members.some(member => member.kind === 'object')
80
+ ? 'a union of message types would require `oneof`, but union arms have no ProtoField<N> tag slot'
81
+ : 'a protobuf decoder root must be an object message';
82
+ this.#refuse(preferredName, reason);
83
+ return { helpers: [], diagnostics: this.#diagnostics };
84
+ }
85
+
86
+ this.#collect(root, preferredName);
87
+ this.#validateRequiredCycles();
88
+ for (const message of this.#objects) this.#renderMessage(message);
89
+ if (this.#diagnostics.length > 0) return { helpers: [], diagnostics: this.#diagnostics };
90
+
91
+ const entry = this.#objectHelpers.get(root);
92
+ if (entry === undefined) {
93
+ this.#refuse(preferredName, 'the protobuf root did not receive a decoder helper');
94
+ return { helpers: [], diagnostics: this.#diagnostics };
95
+ }
96
+ return { entry, helpers: [...this.#auxiliary, ...this.#messages], diagnostics: [] };
97
+ }
98
+
99
+ #collect(node: TypeIR, suggested: string): void {
100
+ collectMessages(node, suggested, (message, name) => {
101
+ if (this.#objectHelpers.has(message)) return false;
102
+ const helper = this.#name(`Decode${safeName(message.name ?? name)}`);
103
+ this.#objectHelpers.set(message, helper);
104
+ if (message.name !== undefined && !this.#namedObjects.has(message.name)) {
105
+ this.#namedObjects.set(message.name, message);
106
+ }
107
+ this.#objects.push(message);
108
+ return true;
109
+ });
110
+ }
111
+
112
+ #validateRequiredCycles(): void {
113
+ const visited = new Set<ObjectIR>();
114
+ const active = new Set<ObjectIR>();
115
+
116
+ const visit = (node: ObjectIR, path: readonly string[]): void => {
117
+ if (visited.has(node)) return;
118
+ active.add(node);
119
+ for (const property of node.properties) {
120
+ if (property.optional) continue;
121
+ const target = this.#requiredMessage(property.type);
122
+ if (target === undefined) continue;
123
+ const next = [...path, property.name];
124
+ if (active.has(target)) {
125
+ this.#refuse(
126
+ next.join('.'),
127
+ 'a cycle of required singular protobuf messages has no finite absent-field value; make one edge optional, nullable, or repeated',
128
+ );
129
+ continue;
130
+ }
131
+ visit(target, next);
132
+ }
133
+ active.delete(node);
134
+ visited.add(node);
135
+ };
136
+
137
+ for (const object of this.#objects) visit(object, [object.name ?? 'Message']);
138
+ }
139
+
140
+ #requiredMessage(node: TypeIR): ObjectIR | undefined {
141
+ switch (node.kind) {
142
+ case 'object':
143
+ return node;
144
+ case 'ref':
145
+ return this.#namedObjects.get(node.name);
146
+ case 'union': {
147
+ if (node.members.some(member => member.kind === 'null' || member.kind === 'undefined')) return undefined;
148
+ const values = node.members.filter(member => member.kind !== 'null' && member.kind !== 'undefined');
149
+ return values.length === 1 && values[0] !== undefined ? this.#requiredMessage(values[0]) : undefined;
150
+ }
151
+ default:
152
+ return undefined;
153
+ }
154
+ }
155
+
156
+ #renderMessage(node: ObjectIR): void {
157
+ const helper = this.#objectHelpers.get(node);
158
+ if (helper === undefined) {
159
+ this.#refuse(node.name ?? 'Message', 'the protobuf message did not receive a decoder helper');
160
+ return;
161
+ }
162
+
163
+ const fields: PlannedField[] = [];
164
+ const ordered = node.properties.toSorted(
165
+ (left, right) => (left.protoField ?? Number.MAX_SAFE_INTEGER) - (right.protoField ?? Number.MAX_SAFE_INTEGER),
166
+ );
167
+ for (const property of ordered) {
168
+ const path = `${node.name ?? 'Message'}.${property.name}`;
169
+ if (!IDENTIFIER.test(property.name)) {
170
+ this.#refuse(path, `\`${property.name}\` is not a valid protobuf field identifier`);
171
+ continue;
172
+ }
173
+ if (property.protoField === undefined) {
174
+ this.#refuse(path, `protobuf property \`${property.name}\` has no ProtoField<N> field number`);
175
+ continue;
176
+ }
177
+ const plan = this.#fieldPlan(property.type, path);
178
+ if (plan === undefined) continue;
179
+ if (property.optional && plan.nullable) {
180
+ this.#refuse(
181
+ path,
182
+ 'an optional nullable protobuf field has three TypeScript states but only two wire-presence states',
183
+ );
184
+ continue;
185
+ }
186
+ fields.push({ property, path, plan });
187
+ }
188
+ if (this.#diagnostics.length > 0) return;
189
+
190
+ const initial = fields
191
+ .flatMap(field => {
192
+ const value = this.#initial(field);
193
+ return value === undefined ? [] : [`[${JSON.stringify(field.property.name)}]: ${value}`];
194
+ })
195
+ .join(', ');
196
+ const lines = [`if (_v === undefined) _v = { ${initial} };`, 'while (!_r.done) {'];
197
+ lines.push(
198
+ 'const _key = _r.key();',
199
+ 'const _field = Math.floor(_key / 8);',
200
+ 'const _wire = _key % 8;',
201
+ 'switch (_field) {',
202
+ );
203
+ for (const field of fields) lines.push(this.#case(field));
204
+ lines.push('default: _r.skip(_wire); break;', '}', '}');
205
+
206
+ for (const field of fields) {
207
+ if (field.property.optional || field.plan.nullable || field.plan.repeated) continue;
208
+ const has = `Object.hasOwn(_v, ${JSON.stringify(field.property.name)})`;
209
+ if (field.plan.atom.kind === 'enum') {
210
+ lines.push(`if (!${has}) ${field.plan.atom.helper}(0);`);
211
+ } else if (field.plan.atom.kind === 'message') {
212
+ lines.push(
213
+ `if (!${has}) ${this.#assign('_v', field.property.name, `${field.plan.atom.helper}(new ${this.#options.reader}())`)};`,
214
+ );
215
+ }
216
+ }
217
+ lines.push('return _v;');
218
+ this.#messages.push(`function ${helper}(_r, _v) { ${lines.join(' ')} }`);
219
+ }
220
+
221
+ #initial(field: PlannedField): string | undefined {
222
+ if (field.plan.repeated) return '[]';
223
+ if (field.plan.nullable) return 'null';
224
+ if (field.property.optional) return undefined;
225
+ switch (field.plan.atom.kind) {
226
+ case 'scalar':
227
+ return field.plan.atom.zero;
228
+ case 'timestamp':
229
+ return 'new Date(0)';
230
+ case 'enum':
231
+ case 'message':
232
+ return undefined;
233
+ }
234
+ }
235
+
236
+ #case(field: PlannedField): string {
237
+ const number = field.property.protoField;
238
+ if (number === undefined) return '';
239
+ const property = access('_v', field.property.name);
240
+ const atom = field.plan.atom;
241
+
242
+ let body: string;
243
+ if (field.plan.repeated && isPacked(atom)) {
244
+ const packed = this.#name('Packed');
245
+ const one = this.#read(atom, '_r');
246
+ const packedOne = this.#read(atom, packed);
247
+ body =
248
+ `if (_wire === ${atom.wire}) { ${property}.push(${one}); } ` +
249
+ `else if (_wire === 2) { const ${packed} = _r.message(); while (!${packed}.done) ${property}.push(${packedOne}); } ` +
250
+ 'else _r.skip(_wire);';
251
+ } else if (field.plan.repeated) {
252
+ body = `_wire === ${atom.wire} ? ${property}.push(${this.#read(atom, '_r')}) : _r.skip(_wire);`;
253
+ } else {
254
+ const current =
255
+ atom.kind === 'message' || atom.kind === 'timestamp'
256
+ ? `Object.hasOwn(_v, ${JSON.stringify(field.property.name)}) ? ${property} : undefined`
257
+ : undefined;
258
+ const value = this.#read(atom, '_r', current);
259
+ body = `if (_wire === ${atom.wire}) { ${this.#assign('_v', field.property.name, value)}; } else _r.skip(_wire);`;
260
+ }
261
+ return `case ${number}: ${body} break;`;
262
+ }
263
+
264
+ #read(atom: AtomPlan, reader: string, current?: string): string {
265
+ switch (atom.kind) {
266
+ case 'scalar':
267
+ return `${reader}.${atom.method}()`;
268
+ case 'enum':
269
+ return `${atom.helper}(${reader}.uint32())`;
270
+ case 'message':
271
+ case 'timestamp':
272
+ return `${atom.helper}(${reader}.message()${current === undefined ? '' : `, ${current}`})`;
273
+ }
274
+ }
275
+
276
+ #assign(target: string, property: string, value: string): string {
277
+ if (property !== '__proto__') return `${access(target, property)} = ${value}`;
278
+ return (
279
+ `Object.defineProperty(${target}, "__proto__", ` +
280
+ `{ value: ${value}, writable: true, enumerable: true, configurable: true })`
281
+ );
282
+ }
283
+
284
+ #fieldPlan(node: TypeIR, path: string): FieldPlan | undefined {
285
+ return planField<AtomPlan>(
286
+ node,
287
+ path,
288
+ (value, location) => this.#atom(value, location),
289
+ (location, values) => this.#enum(location, values),
290
+ (location, reason, origin) => this.#refuse(location, reason, origin),
291
+ );
292
+ }
293
+
294
+ #atom(node: FieldAtomIR, path: string): AtomPlan | undefined {
295
+ switch (node.kind) {
296
+ case 'scalar':
297
+ return this.#scalar(node, path);
298
+ case 'object': {
299
+ const helper = this.#objectHelpers.get(node);
300
+ return helper === undefined
301
+ ? this.#refuse(path, 'the nested protobuf message did not receive a decoder helper')
302
+ : { kind: 'message', helper, wire: 2 };
303
+ }
304
+ case 'ref': {
305
+ const target = this.#namedObjects.get(node.name);
306
+ const helper = target === undefined ? undefined : this.#objectHelpers.get(target);
307
+ return helper === undefined || target === undefined
308
+ ? this.#refuse(path, `protobuf back-reference \`${node.name}\` has no message declaration`)
309
+ : { kind: 'message', helper, wire: 2 };
310
+ }
311
+ }
312
+ }
313
+
314
+ #scalar(node: ScalarIR, path: string): AtomPlan | undefined {
315
+ const plan = planScalar(
316
+ node,
317
+ path,
318
+ () => this.#timestamp(),
319
+ (location, reason, origin) => this.#refuse(location, reason, origin),
320
+ );
321
+ return plan?.kind === 'scalar' ? { ...plan, zero: SCALAR_ZERO[plan.zero] } : plan;
322
+ }
323
+
324
+ #enum(path: string, values: readonly string[]): EnumPlan {
325
+ const helper = this.#name('Enum');
326
+ const cases = values.map((value, index) => `case ${index + 1}: return ${JSON.stringify(value)};`);
327
+ const message = `unknown protobuf enum number for ${path}`;
328
+ this.#auxiliary.push(
329
+ `function ${helper}(_v) { switch (_v) { ${cases.join(' ')} default: throw new RangeError(${JSON.stringify(message)} + ": " + _v); } }`,
330
+ );
331
+ return { kind: 'enum', helper, wire: 0 };
332
+ }
333
+
334
+ #timestamp(): string {
335
+ if (this.#timestampHelper !== undefined) return this.#timestampHelper;
336
+ const helper = this.#name('Timestamp');
337
+ this.#timestampHelper = helper;
338
+ this.#auxiliary.push(
339
+ `function ${helper}(_r, _current) { ` +
340
+ 'let _seconds = 0n; let _nanos = 0; ' +
341
+ 'if (_current !== undefined) { ' +
342
+ 'const _milliseconds = BigInt(_current.getTime()); ' +
343
+ '_seconds = _milliseconds / 1000n; _nanos = Number(_milliseconds % 1000n) * 1000000; ' +
344
+ 'if (_nanos < 0) { _seconds -= 1n; _nanos += 1000000000; } } ' +
345
+ 'while (!_r.done) { const _key = _r.key(); const _field = Math.floor(_key / 8); const _wire = _key % 8; ' +
346
+ 'switch (_field) { ' +
347
+ 'case 1: if (_wire === 0) _seconds = _r.int64(); else _r.skip(_wire); break; ' +
348
+ 'case 2: if (_wire === 0) _nanos = _r.int32(); else _r.skip(_wire); break; ' +
349
+ 'default: _r.skip(_wire); break; } } ' +
350
+ 'if (_seconds < -62135596800n || _seconds > 253402300799n) ' +
351
+ 'throw new RangeError("protobuf Timestamp seconds are outside 0001-01-01 through 9999-12-31"); ' +
352
+ 'if (_nanos < 0 || _nanos > 999999999) ' +
353
+ 'throw new RangeError("protobuf Timestamp nanos must be in 0 … 999999999"); ' +
354
+ 'const _milliseconds = _seconds * 1000n + BigInt(Math.trunc(_nanos / 1000000)); ' +
355
+ 'const _number = Number(_milliseconds); const _date = new Date(_number); ' +
356
+ 'if (!Number.isSafeInteger(_number) || Number.isNaN(_date.getTime())) ' +
357
+ 'throw new RangeError("protobuf Timestamp cannot be represented by a JavaScript Date"); ' +
358
+ 'return _date; }',
359
+ );
360
+ return helper;
361
+ }
362
+
363
+ #name(hint: string): string {
364
+ return `${this.#options.namespace}${hint}${this.#counter++}`;
365
+ }
366
+
367
+ #refuse(path: string, reason: string, source?: string): undefined {
368
+ this.#diagnostics.push(source === undefined ? { path, reason } : { path, reason, source });
369
+ return undefined;
370
+ }
371
+ }
372
+
373
+ function access(target: string, property: string): string {
374
+ return `${target}[${JSON.stringify(property)}]`;
375
+ }