protons 7.7.0 → 8.0.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 (60) hide show
  1. package/README.md +5 -5
  2. package/dist/bin/protons.js +1 -1
  3. package/dist/src/fields/array-field.d.ts +18 -0
  4. package/dist/src/fields/array-field.d.ts.map +1 -0
  5. package/dist/src/fields/array-field.js +83 -0
  6. package/dist/src/fields/array-field.js.map +1 -0
  7. package/dist/src/fields/enum-field.d.ts +9 -0
  8. package/dist/src/fields/enum-field.d.ts.map +1 -0
  9. package/dist/src/fields/enum-field.js +21 -0
  10. package/dist/src/fields/enum-field.js.map +1 -0
  11. package/dist/src/fields/field.d.ts +45 -0
  12. package/dist/src/fields/field.d.ts.map +1 -0
  13. package/dist/src/fields/field.js +147 -0
  14. package/dist/src/fields/field.js.map +1 -0
  15. package/dist/src/fields/map-field.d.ts +22 -0
  16. package/dist/src/fields/map-field.d.ts.map +1 -0
  17. package/dist/src/fields/map-field.js +83 -0
  18. package/dist/src/fields/map-field.js.map +1 -0
  19. package/dist/src/fields/message-field.d.ts +9 -0
  20. package/dist/src/fields/message-field.d.ts.map +1 -0
  21. package/dist/src/fields/message-field.js +23 -0
  22. package/dist/src/fields/message-field.js.map +1 -0
  23. package/dist/src/index.d.ts +190 -16
  24. package/dist/src/index.d.ts.map +1 -1
  25. package/dist/src/index.js +5 -963
  26. package/dist/src/index.js.map +1 -1
  27. package/dist/src/types/enum.d.ts +21 -0
  28. package/dist/src/types/enum.d.ts.map +1 -0
  29. package/dist/src/types/enum.js +87 -0
  30. package/dist/src/types/enum.js.map +1 -0
  31. package/dist/src/types/index.d.ts +20 -0
  32. package/dist/src/types/index.d.ts.map +1 -0
  33. package/dist/src/types/index.js +2 -0
  34. package/dist/src/types/index.js.map +1 -0
  35. package/dist/src/types/message.d.ts +49 -0
  36. package/dist/src/types/message.d.ts.map +1 -0
  37. package/dist/src/types/message.js +478 -0
  38. package/dist/src/types/message.js.map +1 -0
  39. package/dist/src/types/module.d.ts +30 -0
  40. package/dist/src/types/module.d.ts.map +1 -0
  41. package/dist/src/types/module.js +184 -0
  42. package/dist/src/types/module.js.map +1 -0
  43. package/dist/src/types/primitive.d.ts +13 -0
  44. package/dist/src/types/primitive.d.ts.map +1 -0
  45. package/dist/src/types/primitive.js +174 -0
  46. package/dist/src/types/primitive.js.map +1 -0
  47. package/dist/typedoc-urls.json +2 -4
  48. package/package.json +102 -15
  49. package/src/fields/array-field.ts +109 -0
  50. package/src/fields/enum-field.ts +30 -0
  51. package/src/fields/field.ts +201 -0
  52. package/src/fields/map-field.ts +107 -0
  53. package/src/fields/message-field.ts +29 -0
  54. package/src/index.ts +6 -1202
  55. package/src/types/enum.ts +112 -0
  56. package/src/types/index.ts +21 -0
  57. package/src/types/message.ts +558 -0
  58. package/src/types/module.ts +234 -0
  59. package/src/types/primitive.ts +215 -0
  60. package/LICENSE +0 -4
package/src/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- /* eslint-disable max-depth */
2
-
3
1
  /**
4
2
  * @packageDocumentation
5
3
  *
@@ -33,7 +31,7 @@
33
31
  * In your code import the generated classes and use them to transform to/from bytes:
34
32
  *
35
33
  * ```js
36
- * import { Foo } from './foo.js'
34
+ * import { Foo } from './foo.ts'
37
35
  *
38
36
  * const foo = {
39
37
  * message: 'hello world'
@@ -48,7 +46,7 @@
48
46
  *
49
47
  * ## Differences from protobuf.js
50
48
  *
51
- * This module uses the internal reader/writer from `protobuf.js` as it is highly optimised and there's no point reinventing the wheel.
49
+ * This module uses the internal reader/writer from `protobuf.js` as it is highly optimized and there's no point reinventing the wheel.
52
50
  *
53
51
  * It does have one or two differences:
54
52
  *
@@ -197,1020 +195,14 @@ import fs from 'fs/promises'
197
195
  import path from 'path'
198
196
  import { promisify } from 'util'
199
197
  import { main as pbjs } from 'protobufjs-cli/pbjs.js'
200
- import { NoMessagesFoundError, ParseError } from 'protons-runtime'
201
-
202
- export enum CODEC_TYPES {
203
- VARINT = 0,
204
- BIT64,
205
- LENGTH_DELIMITED,
206
- START_GROUP,
207
- END_GROUP,
208
- BIT32
209
- }
198
+ import { Module } from './types/module.ts'
210
199
 
211
200
  function pathWithExtension (input: string, extension: string, outputDir?: string): string {
212
201
  const output = outputDir ?? path.dirname(input)
213
202
  return path.join(output, path.basename(input).split('.').slice(0, -1).join('.') + extension)
214
203
  }
215
204
 
216
- /**
217
- * This will be removed in a future release
218
- *
219
- * @deprecated
220
- */
221
- export class CodeError extends Error {
222
- public code: string
223
-
224
- constructor (message: string, code: string, options?: ErrorOptions) {
225
- super(message, options)
226
-
227
- this.code = code
228
- }
229
- }
230
-
231
- const types: Record<string, string> = {
232
- bool: 'boolean',
233
- bytes: 'Uint8Array',
234
- double: 'number',
235
- fixed32: 'number',
236
- fixed64: 'bigint',
237
- float: 'number',
238
- int32: 'number',
239
- int64: 'bigint',
240
- sfixed32: 'number',
241
- sfixed64: 'bigint',
242
- sint32: 'number',
243
- sint64: 'bigint',
244
- string: 'string',
245
- uint32: 'number',
246
- uint64: 'bigint'
247
- }
248
-
249
- const jsTypeOverrides: Record<string, 'number' | 'string'> = {
250
- JS_NUMBER: 'number',
251
- JS_STRING: 'string'
252
- }
253
-
254
- const encoderGenerators: Record<string, (val: string, jsTypeOverride?: 'number' | 'string') => string> = {
255
- bool: (val) => `w.bool(${val})`,
256
- bytes: (val) => `w.bytes(${val})`,
257
- double: (val) => `w.double(${val})`,
258
- fixed32: (val) => `w.fixed32(${val})`,
259
- fixed64: (val, jsTypeOverride) => {
260
- if (jsTypeOverride === 'number') {
261
- return `w.fixed64Number(${val})`
262
- }
263
-
264
- if (jsTypeOverride === 'string') {
265
- return `w.fixed64String(${val})`
266
- }
267
-
268
- return `w.fixed64(${val})`
269
- },
270
- float: (val) => `w.float(${val})`,
271
- int32: (val) => `w.int32(${val})`,
272
- int64: (val, jsTypeOverride) => {
273
- if (jsTypeOverride === 'number') {
274
- return `w.int64Number(${val})`
275
- }
276
-
277
- if (jsTypeOverride === 'string') {
278
- return `w.int64String(${val})`
279
- }
280
-
281
- return `w.int64(${val})`
282
- },
283
- sfixed32: (val) => `w.sfixed32(${val})`,
284
- sfixed64: (val, jsTypeOverride) => {
285
- if (jsTypeOverride === 'number') {
286
- return `w.sfixed64Number(${val})`
287
- }
288
-
289
- if (jsTypeOverride === 'string') {
290
- return `w.sfixed64String(${val})`
291
- }
292
-
293
- return `w.sfixed64(${val})`
294
- },
295
- sint32: (val) => `w.sint32(${val})`,
296
- sint64: (val, jsTypeOverride) => {
297
- if (jsTypeOverride === 'number') {
298
- return `w.sint64Number(${val})`
299
- }
300
-
301
- if (jsTypeOverride === 'string') {
302
- return `w.sint64String(${val})`
303
- }
304
-
305
- return `w.sint64(${val})`
306
- },
307
- string: (val) => `w.string(${val})`,
308
- uint32: (val) => `w.uint32(${val})`,
309
- uint64: (val, jsTypeOverride) => {
310
- if (jsTypeOverride === 'number') {
311
- return `w.uint64Number(${val})`
312
- }
313
-
314
- if (jsTypeOverride === 'string') {
315
- return `w.uint64String(${val})`
316
- }
317
-
318
- return `w.uint64(${val})`
319
- }
320
- }
321
-
322
- const decoderGenerators: Record<string, (jsTypeOverride?: 'number' | 'string') => string> = {
323
- bool: () => 'reader.bool()',
324
- bytes: () => 'reader.bytes()',
325
- double: () => 'reader.double()',
326
- fixed32: () => 'reader.fixed32()',
327
- fixed64: (jsTypeOverride) => {
328
- if (jsTypeOverride === 'number') {
329
- return 'reader.fixed64Number()'
330
- }
331
-
332
- if (jsTypeOverride === 'string') {
333
- return 'reader.fixed64String()'
334
- }
335
-
336
- return 'reader.fixed64()'
337
- },
338
- float: () => 'reader.float()',
339
- int32: () => 'reader.int32()',
340
- int64: (jsTypeOverride) => {
341
- if (jsTypeOverride === 'number') {
342
- return 'reader.int64Number()'
343
- }
344
-
345
- if (jsTypeOverride === 'string') {
346
- return 'reader.int64String()'
347
- }
348
-
349
- return 'reader.int64()'
350
- },
351
- sfixed32: () => 'reader.sfixed32()',
352
- sfixed64: (jsTypeOverride) => {
353
- if (jsTypeOverride === 'number') {
354
- return 'reader.sfixed64Number()'
355
- }
356
-
357
- if (jsTypeOverride === 'string') {
358
- return 'reader.sfixed64String()'
359
- }
360
-
361
- return 'reader.sfixed64()'
362
- },
363
- sint32: () => 'reader.sint32()',
364
- sint64: (jsTypeOverride) => {
365
- if (jsTypeOverride === 'number') {
366
- return 'reader.sint64Number()'
367
- }
368
-
369
- if (jsTypeOverride === 'string') {
370
- return 'reader.sint64String()'
371
- }
372
-
373
- return 'reader.sint64()'
374
- },
375
- string: () => 'reader.string()',
376
- uint32: () => 'reader.uint32()',
377
- uint64: (jsTypeOverride) => {
378
- if (jsTypeOverride === 'number') {
379
- return 'reader.uint64Number()'
380
- }
381
-
382
- if (jsTypeOverride === 'string') {
383
- return 'reader.uint64String()'
384
- }
385
-
386
- return 'reader.uint64()'
387
- }
388
- }
389
-
390
- const defaultValueGenerators: Record<string, () => string> = {
391
- bool: () => 'false',
392
- bytes: () => 'uint8ArrayAlloc(0)',
393
- double: () => '0',
394
- fixed32: () => '0',
395
- fixed64: () => '0n',
396
- float: () => '0',
397
- int32: () => '0',
398
- int64: () => '0n',
399
- sfixed32: () => '0',
400
- sfixed64: () => '0n',
401
- sint32: () => '0',
402
- sint64: () => '0n',
403
- string: () => "''",
404
- uint32: () => '0',
405
- uint64: () => '0n'
406
- }
407
-
408
- const defaultValueGeneratorsJsTypeOverrides: Record<string, () => string> = {
409
- number: () => '0',
410
- string: () => "''"
411
- }
412
-
413
- const defaultValueTestGenerators: Record<string, (field: string) => string> = {
414
- bool: (field) => `(${field} != null && ${field} !== false)`,
415
- bytes: (field) => `(${field} != null && ${field}.byteLength > 0)`,
416
- double: (field) => `(${field} != null && ${field} !== 0)`,
417
- fixed32: (field) => `(${field} != null && ${field} !== 0)`,
418
- fixed64: (field) => `(${field} != null && ${field} !== 0n)`,
419
- float: (field) => `(${field} != null && ${field} !== 0)`,
420
- int32: (field) => `(${field} != null && ${field} !== 0)`,
421
- int64: (field) => `(${field} != null && ${field} !== 0n)`,
422
- sfixed32: (field) => `(${field} != null && ${field} !== 0)`,
423
- sfixed64: (field) => `(${field} != null && ${field} !== 0n)`,
424
- sint32: (field) => `(${field} != null && ${field} !== 0)`,
425
- sint64: (field) => `(${field} != null && ${field} !== 0n)`,
426
- string: (field) => `(${field} != null && ${field} !== '')`,
427
- uint32: (field) => `(${field} != null && ${field} !== 0)`,
428
- uint64: (field) => `(${field} != null && ${field} !== 0n)`
429
- }
430
-
431
- const defaultValueTestGeneratorsJsTypeOverrides: Record<string, (field: string) => string> = {
432
- number: (field) => `(${field} != null && ${field} !== 0)`,
433
- string: (field) => `(${field} != null && ${field} !== '')`
434
- }
435
-
436
- function findJsTypeOverride (defaultType: string, fieldDef: FieldDef): 'number' | 'string' | undefined {
437
- if (fieldDef.options?.jstype != null && jsTypeOverrides[fieldDef.options?.jstype] != null) {
438
- if (!['int64', 'uint64', 'sint64', 'fixed64', 'sfixed64'].includes(defaultType)) {
439
- throw new Error(`jstype is only allowed on int64, uint64, sint64, fixed64 or sfixed64 fields - got "${defaultType}"`)
440
- }
441
-
442
- return jsTypeOverrides[fieldDef.options?.jstype]
443
- }
444
- }
445
-
446
- function findJsTypeName (typeName: string, classDef: MessageDef, moduleDef: ModuleDef, fieldDef: FieldDef): string {
447
- const override = findJsTypeOverride(typeName, fieldDef)
448
-
449
- if (override != null) {
450
- return override
451
- }
452
-
453
- if (types[typeName] != null) {
454
- return types[typeName]
455
- }
456
-
457
- if (isEnumDef(classDef)) {
458
- throw new Error('Could not find type in enum')
459
- }
460
-
461
- if (classDef.nested?.[typeName] != null) {
462
- return `${classDef.fullName}.${typeName}`
463
- }
464
-
465
- if (classDef.parent != null) {
466
- return findJsTypeName(typeName, classDef.parent, moduleDef, fieldDef)
467
- }
468
-
469
- if (moduleDef.globals[typeName] != null) {
470
- return typeName
471
- }
472
-
473
- throw new Error(`Could not resolve type name "${typeName}"`)
474
- }
475
-
476
- function findDef (typeName: string, classDef: MessageDef, moduleDef: ModuleDef): MessageDef {
477
- if (isEnumDef(classDef)) {
478
- throw new Error('Could not find type in enum')
479
- }
480
-
481
- if (classDef.nested?.[typeName] != null) {
482
- return classDef.nested?.[typeName]
483
- }
484
-
485
- if (classDef.parent != null) {
486
- return findDef(typeName, classDef.parent, moduleDef)
487
- }
488
-
489
- if (moduleDef.globals[typeName] != null) {
490
- return moduleDef.globals[typeName]
491
- }
492
-
493
- throw new Error(`Could not resolve type name "${typeName}"`)
494
- }
495
-
496
- function createDefaultObject (fields: Record<string, FieldDef>, messageDef: MessageDef, moduleDef: ModuleDef): string {
497
- const output = Object.entries(fields)
498
- .map(([name, fieldDef]) => {
499
- if (fieldDef.map) {
500
- return `${name}: new Map<${types[fieldDef.keyType ?? 'string']}, ${types[fieldDef.valueType]}>()`
501
- }
502
-
503
- if (fieldDef.repeated) {
504
- return `${name}: []`
505
- }
506
-
507
- if (fieldDef.optional) {
508
- return ''
509
- }
510
-
511
- const type: string = fieldDef.type
512
- let defaultValue
513
- let defaultValueGenerator = defaultValueGenerators[type]
514
-
515
- if (defaultValueGenerator != null) {
516
- const jsTypeOverride = findJsTypeOverride(type, fieldDef)
517
-
518
- if (jsTypeOverride != null && defaultValueGeneratorsJsTypeOverrides[jsTypeOverride] != null) {
519
- defaultValueGenerator = defaultValueGeneratorsJsTypeOverrides[jsTypeOverride]
520
- }
521
-
522
- if (type === 'bytes') {
523
- moduleDef.addImport('uint8arrays/alloc', 'alloc', 'uint8ArrayAlloc')
524
- }
525
-
526
- defaultValue = defaultValueGenerator()
527
- } else {
528
- const def = findDef(fieldDef.type, messageDef, moduleDef)
529
-
530
- if (isEnumDef(def)) {
531
- // select lowest-value enum - should be 0 but it's not guaranteed
532
- const val = Object.entries(def.values)
533
- .sort((a, b) => {
534
- if (a[1] < b[1]) {
535
- return 1
536
- }
537
-
538
- if (a[1] > b[1]) {
539
- return -1
540
- }
541
-
542
- return 0
543
- })
544
- .pop()
545
-
546
- if (val == null) {
547
- throw new Error(`Could not find default enum value for ${def.fullName}`)
548
- }
549
-
550
- defaultValue = `${def.name}.${val[0]}`
551
- } else {
552
- defaultValue = 'undefined'
553
- }
554
- }
555
-
556
- return `${name}: ${defaultValue}`
557
- })
558
- .filter(Boolean)
559
- .join(',\n ')
560
-
561
- if (output !== '') {
562
- return `
563
- ${output}
564
- `
565
- }
566
-
567
- return ''
568
- }
569
-
570
- const encoders: Record<string, string> = {
571
- bool: 'bool',
572
- bytes: 'bytes',
573
- double: 'double',
574
- fixed32: 'fixed32',
575
- fixed64: 'fixed64',
576
- float: 'float',
577
- int32: 'int32',
578
- int64: 'int64',
579
- sfixed32: 'sfixed32',
580
- sfixed64: 'sfixed64',
581
- sint32: 'sint32',
582
- sint64: 'sint64',
583
- string: 'string',
584
- uint32: 'uint32',
585
- uint64: 'uint64'
586
- }
587
-
588
- const codecTypes: Record<string, CODEC_TYPES> = {
589
- bool: CODEC_TYPES.VARINT,
590
- bytes: CODEC_TYPES.LENGTH_DELIMITED,
591
- double: CODEC_TYPES.BIT64,
592
- enum: CODEC_TYPES.VARINT,
593
- fixed32: CODEC_TYPES.BIT32,
594
- fixed64: CODEC_TYPES.BIT64,
595
- float: CODEC_TYPES.BIT32,
596
- int32: CODEC_TYPES.VARINT,
597
- int64: CODEC_TYPES.VARINT,
598
- message: CODEC_TYPES.LENGTH_DELIMITED,
599
- sfixed32: CODEC_TYPES.BIT32,
600
- sfixed64: CODEC_TYPES.BIT64,
601
- sint32: CODEC_TYPES.VARINT,
602
- sint64: CODEC_TYPES.VARINT,
603
- string: CODEC_TYPES.LENGTH_DELIMITED,
604
- uint32: CODEC_TYPES.VARINT,
605
- uint64: CODEC_TYPES.VARINT
606
- }
607
-
608
- interface ClassDef {
609
- name: string
610
- fullName: string
611
- parent?: ClassDef
612
- fields?: Record<string, FieldDef>
613
- nested?: Record<string, ClassDef>
614
- oneofs?: Array<string[]>
615
- }
616
-
617
- interface EnumDef {
618
- name: string
619
- fullName: string
620
- parent?: ClassDef
621
- values: Record<string, number>
622
- }
623
-
624
- type MessageDef = ClassDef | EnumDef
625
-
626
- function isEnumDef (obj: any): obj is EnumDef {
627
- return obj.values != null
628
- }
629
-
630
- interface FieldDef {
631
- type: string
632
- id: number
633
- options?: Record<string, any>
634
- rule: string
635
- optional: boolean
636
- repeated: boolean
637
- lengthLimit?: number
638
- message: boolean
639
- enum: boolean
640
- map: boolean
641
- valueType: string
642
- keyType: string
643
-
644
- /**
645
- * Support proto2 required field. This field means a value should always be
646
- * in the serialized buffer, any message without it should be considered
647
- * invalid. It was removed for proto3.
648
- */
649
- proto2Required: boolean
650
- }
651
-
652
- function defineFields (fields: Record<string, FieldDef>, messageDef: MessageDef, moduleDef: ModuleDef): string[] {
653
- return Object.entries(fields).map(([fieldName, fieldDef]) => {
654
- if (fieldDef.map) {
655
- return `${fieldName}: Map<${findJsTypeName(fieldDef.keyType ?? 'string', messageDef, moduleDef, fieldDef)}, ${findJsTypeName(fieldDef.valueType, messageDef, moduleDef, fieldDef)}>`
656
- }
657
-
658
- return `${fieldName}${fieldDef.optional ? '?' : ''}: ${findJsTypeName(fieldDef.type, messageDef, moduleDef, fieldDef)}${fieldDef.repeated ? '[]' : ''}`
659
- })
660
- }
661
-
662
- function compileMessage (messageDef: MessageDef, moduleDef: ModuleDef, flags?: Flags): string {
663
- if (isEnumDef(messageDef)) {
664
- moduleDef.addImport('protons-runtime', 'enumeration')
665
-
666
- // check that the enum def values start from 0
667
- if (Object.values(messageDef.values)[0] !== 0) {
668
- const message = `enum ${messageDef.name} does not contain a value that maps to zero as it's first element, this is required in proto3 - see https://protobuf.dev/programming-guides/proto3/#enum`
669
-
670
- if (flags?.strict === true) {
671
- throw new ParseError(message)
672
- } else {
673
- // eslint-disable-next-line no-console
674
- console.info(`[WARN] ${message}`)
675
- }
676
- }
677
-
678
- return `
679
- export enum ${messageDef.name} {
680
- ${
681
- Object.keys(messageDef.values).map(name => {
682
- return `${name} = '${name}'`
683
- }).join(',\n ').trim()
684
- }
685
- }
686
-
687
- enum __${messageDef.name}Values {
688
- ${
689
- Object.entries(messageDef.values).map(([name, value]) => {
690
- return `${name} = ${value}`
691
- }).join(',\n ').trim()
692
- }
693
- }
694
-
695
- export namespace ${messageDef.name} {
696
- export const codec = (): Codec<${messageDef.name}> => {
697
- return enumeration<${messageDef.name}>(__${messageDef.name}Values)
698
- }
699
- }
700
- `.trimStart()
701
- }
702
-
703
- let nested = ''
704
-
705
- if (messageDef.nested != null) {
706
- nested = '\n'
707
- nested += Object.values(messageDef.nested)
708
- .map(def => compileMessage(def, moduleDef, flags).trim())
709
- .join('\n\n')
710
- .split('\n')
711
- .map(line => line.trim() === '' ? '' : ` ${line}`)
712
- .join('\n')
713
- }
714
-
715
- const fields = messageDef.fields ?? {}
716
-
717
- // import relevant modules
718
- moduleDef.addImport('protons-runtime', 'encodeMessage')
719
- moduleDef.addImport('protons-runtime', 'decodeMessage')
720
- moduleDef.addImport('protons-runtime', 'message')
721
- moduleDef.addTypeImport('protons-runtime', 'Codec')
722
- moduleDef.addTypeImport('protons-runtime', 'DecodeOptions')
723
- moduleDef.addTypeImport('uint8arraylist', 'Uint8ArrayList')
724
-
725
- const interfaceFields = defineFields(fields, messageDef, moduleDef)
726
- .join('\n ')
727
- .trim()
728
-
729
- let interfaceDef = ''
730
- let interfaceCodecDef = ''
731
-
732
- if (interfaceFields === '') {
733
- interfaceDef = `
734
- export interface ${messageDef.name} {}`
735
- } else {
736
- interfaceDef = `
737
- export interface ${messageDef.name} {
738
- ${
739
- defineFields(fields, messageDef, moduleDef)
740
- .join('\n ')
741
- .trim()
742
- }
743
- }`
744
- }
745
-
746
- const encodeFields = Object.entries(fields)
747
- .map(([name, fieldDef]) => {
748
- let codec: string = encoders[fieldDef.type]
749
- let type: string = fieldDef.map ? 'message' : fieldDef.type
750
- let typeName: string = ''
751
-
752
- if (codec == null) {
753
- if (fieldDef.enum) {
754
- moduleDef.addImport('protons-runtime', 'enumeration')
755
- type = 'enum'
756
- } else {
757
- moduleDef.addImport('protons-runtime', 'message')
758
- type = 'message'
759
- }
760
-
761
- typeName = findJsTypeName(fieldDef.type, messageDef, moduleDef, fieldDef)
762
- codec = `${typeName}.codec()`
763
- }
764
-
765
- let valueTest = `obj.${name} != null`
766
-
767
- if (fieldDef.map) {
768
- valueTest = `obj.${name} != null && obj.${name}.size !== 0`
769
- } else if (!fieldDef.optional && !fieldDef.repeated && !fieldDef.proto2Required) {
770
- let defaultValueTestGenerator = defaultValueTestGenerators[type]
771
-
772
- // proto3 singular fields should only be written out if they are not the default value
773
- if (defaultValueTestGenerator != null) {
774
- const jsTypeOverride = findJsTypeOverride(type, fieldDef)
775
-
776
- if (jsTypeOverride != null && defaultValueTestGeneratorsJsTypeOverrides[jsTypeOverride] != null) {
777
- defaultValueTestGenerator = defaultValueTestGeneratorsJsTypeOverrides[jsTypeOverride]
778
- }
779
-
780
- valueTest = `${defaultValueTestGenerator(`obj.${name}`)}`
781
- } else if (type === 'enum') {
782
- // handle enums
783
- const def = findDef(fieldDef.type, messageDef, moduleDef)
784
-
785
- if (!isEnumDef(def)) {
786
- throw new Error(`${fieldDef.type} was not enum def`)
787
- }
788
-
789
- valueTest = `obj.${name} != null`
790
-
791
- // singular enums default to 0, but enums can be defined without a 0
792
- // value which is against the proto3 spec but is tolerated
793
- if (Object.values(def.values)[0] === 0) {
794
- valueTest += ` && __${fieldDef.type}Values[obj.${name}] !== 0`
795
- }
796
- }
797
- }
798
-
799
- function createWriteField (valueVar: string): () => string {
800
- const id = (fieldDef.id << 3) | codecTypes[type]
801
-
802
- if (fieldDef.enum) {
803
- const def = findDef(fieldDef.type, messageDef, moduleDef)
804
-
805
- if (!isEnumDef(def)) {
806
- throw new Error(`${fieldDef.type} was not enum def`)
807
- }
808
- }
809
-
810
- let writeField = (): string => {
811
- const encoderGenerator = encoderGenerators[type]
812
- const jsTypeOverride = findJsTypeOverride(type, fieldDef)
813
-
814
- return `w.uint32(${id})
815
- ${encoderGenerator == null ? `${codec}.encode(${valueVar}, w)` : encoderGenerator(valueVar, jsTypeOverride)}`
816
- }
817
-
818
- if (type === 'message') {
819
- // message fields are only written if they have values. But if a message
820
- // is part of a repeated field, and consists of only default values it
821
- // won't be written, so write a zero-length buffer if that's the case
822
- writeField = (): string => `w.uint32(${id})
823
- ${typeName}.codec().encode(${valueVar}, w)`
824
- }
825
-
826
- return writeField
827
- }
828
-
829
- let writeField = createWriteField(`obj.${name}`)
830
-
831
- if (fieldDef.repeated) {
832
- if (fieldDef.map) {
833
- writeField = () => `
834
- for (const [key, value] of obj.${name}.entries()) {
835
- ${
836
- createWriteField('{ key, value }')()
837
- .split('\n')
838
- .map(s => {
839
- const trimmed = s.trim()
840
-
841
- return trimmed === '' ? trimmed : ` ${s}`
842
- })
843
- .join('\n')
844
- }
845
- }
846
- `.trim()
847
- } else {
848
- writeField = () => `
849
- for (const value of obj.${name}) {
850
- ${
851
- createWriteField('value')()
852
- .split('\n')
853
- .map(s => {
854
- const trimmed = s.trim()
855
-
856
- return trimmed === '' ? trimmed : ` ${s}`
857
- })
858
- .join('\n')
859
- }
860
- }
861
- `.trim()
862
- }
863
- }
864
-
865
- return `
866
- if (${valueTest}) {
867
- ${writeField()}
868
- }`
869
- }).join('\n')
870
-
871
- const enforceOneOfEncoding = createOneOfEncoding(messageDef)
872
- const enforceOneOfDecoding = createOneOfDecoding(messageDef)
873
-
874
- const decodeFields = Object.entries(fields)
875
- .map(([fieldName, fieldDef]) => {
876
- function createReadField (fieldName: string, fieldDef: FieldDef): string {
877
- let codec: string = encoders[fieldDef.type]
878
- let type: string = fieldDef.type
879
-
880
- if (codec == null) {
881
- if (fieldDef.enum) {
882
- moduleDef.addImport('protons-runtime', 'enumeration')
883
- type = 'enum'
884
- } else {
885
- moduleDef.addImport('protons-runtime', 'message')
886
- type = 'message'
887
- }
888
-
889
- const typeName = findJsTypeName(fieldDef.type, messageDef, moduleDef, fieldDef)
890
- codec = `${typeName}.codec()`
891
- }
892
-
893
- // override setting type on js object
894
- const jsTypeOverride = findJsTypeOverride(fieldDef.type, fieldDef)
895
-
896
- let fieldOpts = ''
897
-
898
- if (fieldDef.message) {
899
- let suffix = ''
900
-
901
- if (fieldDef.repeated) {
902
- suffix = '$'
903
- }
904
-
905
- fieldOpts = `, {
906
- limits: opts.limits?.${fieldName}${suffix}
907
- }`
908
- }
909
-
910
- if (fieldDef.map) {
911
- fieldOpts = `, {
912
- limits: {
913
- value: opts.limits?.${fieldName}$value
914
- }
915
- }`
916
-
917
- // do not pass limit opts to map value types that are enums or
918
- // primitives - only support messages
919
- if (types[fieldDef.valueType] != null) {
920
- // primmitive type
921
- fieldOpts = ''
922
- } else {
923
- const valueType = findDef(fieldDef.valueType, messageDef, moduleDef)
924
-
925
- if (isEnumDef(valueType)) {
926
- // enum type
927
- fieldOpts = ''
928
- }
929
- }
930
- }
931
-
932
- const parseValue = `${decoderGenerators[type] == null
933
- ? `${codec}.decode(reader${type === 'message'
934
- ? `, reader.uint32()${fieldOpts}`
935
- : ''})`
936
- : decoderGenerators[type](jsTypeOverride)}`
937
-
938
- if (fieldDef.map) {
939
- moduleDef.addImport('protons-runtime', 'MaxSizeError')
940
-
941
- let limit = `
942
- if (opts.limits?.${fieldName} != null && obj.${fieldName}.size === opts.limits.${fieldName}) {
943
- throw new MaxSizeError('Decode error - map field "${fieldName}" had too many elements')
944
- }
945
- `
946
-
947
- if (fieldDef.lengthLimit != null) {
948
- limit += `
949
- if (obj.${fieldName}.size === ${fieldDef.lengthLimit}) {
950
- throw new MaxSizeError('Decode error - map field "${fieldName}" had too many elements')
951
- }
952
- `
953
- }
954
-
955
- return `case ${fieldDef.id}: {${limit}
956
- const entry = ${parseValue}
957
- obj.${fieldName}.set(entry.key, entry.value)
958
- break
959
- }`
960
- } else if (fieldDef.repeated) {
961
- moduleDef.addImport('protons-runtime', 'MaxLengthError')
962
-
963
- let limit = `
964
- if (opts.limits?.${fieldName} != null && obj.${fieldName}.length === opts.limits.${fieldName}) {
965
- throw new MaxLengthError('Decode error - map field "${fieldName}" had too many elements')
966
- }
967
- `
968
-
969
- if (fieldDef.lengthLimit != null) {
970
- limit += `
971
- if (obj.${fieldName}.length === ${fieldDef.lengthLimit}) {
972
- throw new MaxLengthError('Decode error - repeated field "${fieldName}" had too many elements')
973
- }
974
- `
975
- }
976
-
977
- return `case ${fieldDef.id}: {${limit}
978
- obj.${fieldName}.push(${parseValue})
979
- break
980
- }`
981
- }
982
-
983
- return `case ${fieldDef.id}: {
984
- obj.${fieldName} = ${parseValue}
985
- break
986
- }`
987
- }
988
-
989
- return createReadField(fieldName, fieldDef)
990
- })
991
- .join('\n ')
992
-
993
- interfaceCodecDef = `
994
- let _codec: Codec<${messageDef.name}>
995
-
996
- export const codec = (): Codec<${messageDef.name}> => {
997
- if (_codec == null) {
998
- _codec = message<${messageDef.name}>((obj, w, opts = {}) => {
999
- if (opts.lengthDelimited !== false) {
1000
- w.fork()
1001
- }
1002
- ${enforceOneOfEncoding}${encodeFields === '' ? '' : `${encodeFields}\n`}
1003
- if (opts.lengthDelimited !== false) {
1004
- w.ldelim()
1005
- }
1006
- }, (reader, length, opts = {}) => {
1007
- const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}}
1008
-
1009
- const end = length == null ? reader.len : reader.pos + length
1010
-
1011
- while (reader.pos < end) {
1012
- const tag = reader.uint32()
1013
-
1014
- switch (tag >>> 3) {${decodeFields === '' ? '' : `\n ${decodeFields}`}
1015
- default: {
1016
- reader.skipType(tag & 7)
1017
- break
1018
- }
1019
- }
1020
- }
1021
- ${enforceOneOfDecoding === '' ? '' : `${enforceOneOfDecoding}\n`}
1022
- return obj
1023
- })
1024
- }
1025
-
1026
- return _codec
1027
- }
1028
-
1029
- export const encode = (obj: Partial<${messageDef.name}>): Uint8Array => {
1030
- return encodeMessage(obj, ${messageDef.name}.codec())
1031
- }
1032
-
1033
- export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<${messageDef.name}>): ${messageDef.name} => {
1034
- return decodeMessage(buf, ${messageDef.name}.codec(), opts)
1035
- }`
1036
-
1037
- return `
1038
- ${interfaceDef}
1039
-
1040
- export namespace ${messageDef.name} {
1041
- ${`${nested}${nested !== '' && interfaceCodecDef !== '' ? '\n' : ''}${interfaceCodecDef}`.trim()}
1042
- }
1043
- `.trimStart()
1044
- }
1045
-
1046
- interface Import {
1047
- symbol: string
1048
- alias?: string
1049
- type: boolean
1050
- }
1051
-
1052
- class ModuleDef {
1053
- imports: Map<string, Import[]>
1054
- types: Set<string>
1055
- compiled: string[]
1056
- globals: Record<string, ClassDef>
1057
-
1058
- constructor () {
1059
- this.imports = new Map()
1060
- this.types = new Set()
1061
- this.compiled = []
1062
- this.globals = {}
1063
- }
1064
-
1065
- addImport (module: string, symbol: string, alias?: string): void {
1066
- const defs = this._findDefs(module)
1067
-
1068
- for (const def of defs) {
1069
- // check if we already have a definition for this symbol
1070
- if (def.symbol === symbol) {
1071
- if (alias !== def.alias) {
1072
- throw new Error(`Type symbol ${symbol} imported from ${module} with alias ${def.alias} does not match alias ${alias}`)
1073
- }
1074
-
1075
- // if it was a type before it's not now
1076
- def.type = false
1077
- return
1078
- }
1079
- }
1080
-
1081
- defs.push({
1082
- symbol,
1083
- alias,
1084
- type: false
1085
- })
1086
- }
1087
-
1088
- addTypeImport (module: string, symbol: string, alias?: string): void {
1089
- const defs = this._findDefs(module)
1090
-
1091
- for (const def of defs) {
1092
- // check if we already have a definition for this symbol
1093
- if (def.symbol === symbol) {
1094
- if (alias !== def.alias) {
1095
- throw new Error(`Type symbol ${symbol} imported from ${module} with alias ${def.alias} does not match alias ${alias}`)
1096
- }
1097
-
1098
- return
1099
- }
1100
- }
1101
-
1102
- defs.push({
1103
- symbol,
1104
- alias,
1105
- type: true
1106
- })
1107
- }
1108
-
1109
- _findDefs (module: string): Import[] {
1110
- let defs = this.imports.get(module)
1111
-
1112
- if (defs == null) {
1113
- defs = []
1114
- this.imports.set(module, defs)
1115
- }
1116
-
1117
- return defs
1118
- }
1119
- }
1120
-
1121
- function defineModule (def: ClassDef, flags: Flags): ModuleDef {
1122
- const moduleDef = new ModuleDef()
1123
- const defs = def.nested
1124
-
1125
- if (defs == null) {
1126
- throw new NoMessagesFoundError('No top-level messages found in protobuf')
1127
- }
1128
-
1129
- function defineMessage (defs: Record<string, ClassDef>, parent?: ClassDef, flags?: Flags): void {
1130
- for (const className of Object.keys(defs)) {
1131
- const classDef = defs[className]
1132
-
1133
- classDef.name = className
1134
- classDef.parent = parent
1135
- classDef.fullName = parent == null ? className : `${parent.fullName}.${className}`
1136
-
1137
- if (classDef.nested != null) {
1138
- defineMessage(classDef.nested, classDef)
1139
- }
1140
-
1141
- if (classDef.fields != null) {
1142
- for (const name of Object.keys(classDef.fields)) {
1143
- const fieldDef = classDef.fields[name]
1144
- fieldDef.repeated = fieldDef.rule === 'repeated'
1145
- fieldDef.optional = !fieldDef.repeated && fieldDef.options?.proto3_optional === true
1146
- fieldDef.map = fieldDef.keyType != null
1147
- fieldDef.lengthLimit = fieldDef.options?.['(protons.options).limit']
1148
- fieldDef.proto2Required = false
1149
-
1150
- if (fieldDef.rule === 'required') {
1151
- const message = `field "${name}" is required, this is not allowed in proto3. Please convert your proto2 definitions to proto3 - see https://github.com/ipfs/protons/wiki/Required-fields-and-protobuf-3`
1152
-
1153
- if (flags?.strict === true) {
1154
- throw new ParseError(message)
1155
- } else {
1156
- fieldDef.proto2Required = true
1157
-
1158
- // eslint-disable-next-line no-console
1159
- console.info(`[WARN] ${message}`)
1160
- }
1161
- }
1162
- }
1163
- }
1164
-
1165
- if (parent == null) {
1166
- moduleDef.globals[className] = classDef
1167
- }
1168
- }
1169
- }
1170
-
1171
- function updateTypes (defs: Record<string, ClassDef>, parent?: ClassDef): void {
1172
- for (const className of Object.keys(defs)) {
1173
- const classDef = defs[className]
1174
-
1175
- if (classDef.nested != null) {
1176
- updateTypes(classDef.nested, classDef)
1177
- }
1178
-
1179
- if (classDef.fields != null) {
1180
- for (const name of Object.keys(classDef.fields)) {
1181
- const fieldDef = classDef.fields[name]
1182
- if (types[fieldDef.type] == null) {
1183
- const def = findDef(fieldDef.type, classDef, moduleDef)
1184
-
1185
- fieldDef.enum = isEnumDef(def)
1186
- fieldDef.message = !fieldDef.enum
1187
-
1188
- if (fieldDef.message && !fieldDef.repeated) {
1189
- // the default type for a message is unset so they are always optional
1190
- // https://developers.google.com/protocol-buffers/docs/proto3#default
1191
- fieldDef.optional = true
1192
- }
1193
- }
1194
- }
1195
- }
1196
- }
1197
- }
1198
-
1199
- defineMessage(defs, undefined, flags)
1200
-
1201
- // set enum/message fields now all messages have been defined
1202
- updateTypes(defs)
1203
-
1204
- for (const className of Object.keys(defs)) {
1205
- const classDef = defs[className]
1206
-
1207
- moduleDef.compiled.push(compileMessage(classDef, moduleDef, flags))
1208
- }
1209
-
1210
- return moduleDef
1211
- }
1212
-
1213
- interface Flags {
205
+ export interface Flags {
1214
206
  /**
1215
207
  * Specifies an output directory
1216
208
  */
@@ -1240,197 +232,9 @@ export async function generate (source: string, flags: Flags): Promise<void> {
1240
232
  }
1241
233
 
1242
234
  const def = JSON.parse(json)
1243
-
1244
- for (const [className, classDef] of Object.entries<any>(def.nested ?? {})) {
1245
- for (const [fieldName, fieldDef] of Object.entries<any>(classDef.fields ?? {})) {
1246
- if (fieldDef.keyType == null) {
1247
- continue
1248
- }
1249
-
1250
- // https://developers.google.com/protocol-buffers/docs/proto3#backwards_compatibility
1251
- const mapEntryType = `${className}$${fieldName}Entry`
1252
-
1253
- classDef.nested = classDef.nested ?? {}
1254
- classDef.nested[mapEntryType] = {
1255
- fields: {
1256
- key: {
1257
- type: fieldDef.keyType,
1258
- id: 1
1259
- },
1260
- value: {
1261
- type: fieldDef.type,
1262
- id: 2
1263
- }
1264
- }
1265
- }
1266
-
1267
- fieldDef.valueType = fieldDef.type
1268
- fieldDef.type = mapEntryType
1269
- fieldDef.rule = 'repeated'
1270
- }
1271
-
1272
- configureOnOfs(classDef)
1273
- }
1274
-
1275
- const moduleDef = defineModule(def, flags)
1276
-
1277
- const ignores = [
1278
- '/* eslint-disable import/export */',
1279
- '/* eslint-disable complexity */',
1280
- '/* eslint-disable @typescript-eslint/no-namespace */',
1281
- '/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */',
1282
- '/* eslint-disable @typescript-eslint/no-empty-interface */',
1283
- '/* eslint-disable import/consistent-type-specifier-style */',
1284
- '/* eslint-disable @typescript-eslint/no-unused-vars */'
1285
- ]
1286
-
1287
- const imports = []
1288
- const importedModules = Array.from([...moduleDef.imports.entries()])
1289
- .sort((a, b) => {
1290
- return a[0].localeCompare(b[0])
1291
- })
1292
- .sort((a, b) => {
1293
- const aAllTypes = a[1].reduce((acc, curr) => {
1294
- return acc && curr.type
1295
- }, true)
1296
-
1297
- const bAllTypes = b[1].reduce((acc, curr) => {
1298
- return acc && curr.type
1299
- }, true)
1300
-
1301
- if (aAllTypes && !bAllTypes) {
1302
- return 1
1303
- }
1304
-
1305
- if (!aAllTypes && bAllTypes) {
1306
- return -1
1307
- }
1308
-
1309
- return 0
1310
- })
1311
-
1312
- // add imports
1313
- for (const imp of importedModules) {
1314
- const symbols = imp[1]
1315
- .filter(imp => !imp.type)
1316
- .sort((a, b) => {
1317
- return a.symbol.localeCompare(b.symbol)
1318
- }).map(imp => {
1319
- return `${imp.symbol}${imp.alias != null ? ` as ${imp.alias}` : ''}`
1320
- }).join(', ')
1321
-
1322
- if (symbols.length > 0) {
1323
- imports.push(`import { ${symbols} } from '${imp[0]}'`)
1324
- }
1325
- }
1326
-
1327
- // add type imports
1328
- for (const imp of importedModules) {
1329
- const symbols = imp[1]
1330
- .filter(imp => imp.type)
1331
- .sort((a, b) => {
1332
- return a.symbol.localeCompare(b.symbol)
1333
- }).map(imp => {
1334
- return `${imp.symbol}${imp.alias != null ? ` as ${imp.alias}` : ''}`
1335
- }).join(', ')
1336
-
1337
- if (symbols.length > 0) {
1338
- imports.push(`import type { ${symbols} } from '${imp[0]}'`)
1339
- }
1340
- }
1341
-
1342
- const lines = [
1343
- ...ignores,
1344
- '',
1345
- ...imports,
1346
- '',
1347
- ...moduleDef.compiled
1348
- ]
1349
-
1350
- const content = lines.join('\n').trim()
235
+ const module = new Module(def, flags)
236
+ const content = module.compile()
1351
237
  const outputPath = pathWithExtension(source, '.ts', flags.output)
1352
238
 
1353
239
  await fs.writeFile(outputPath, content + '\n')
1354
240
  }
1355
-
1356
- function configureOnOfs (classDef: any): void {
1357
- const oneOfs: Record<string, { oneof: string[] }> = classDef.oneofs ?? {}
1358
-
1359
- for (const [, { oneof: fields }] of Object.entries(oneOfs)) {
1360
- fields.forEach(field => {
1361
- classDef.fields[field].options ??= {}
1362
- classDef.fields[field].options.proto3_optional = true
1363
- classDef.fields[field].oneof = fields
1364
- })
1365
- }
1366
-
1367
- const oneofs: Record<string, { oneof: string[] }> = classDef.oneofs ?? {}
1368
-
1369
- // reverse order of oneofs as spec says last field overwrites all others
1370
- classDef.oneofs = Object.values(oneofs).map(({ oneof }) => oneof.reverse())
1371
-
1372
- for (const nestedDef of Object.values(classDef.nested ?? {})) {
1373
- configureOnOfs(nestedDef)
1374
- }
1375
- }
1376
-
1377
- function createOneOfEncoding (messageDef: ClassDef): string {
1378
- if (messageDef.oneofs == null) {
1379
- return ''
1380
- }
1381
-
1382
- let oneofs = messageDef.oneofs.map(fields => {
1383
- if (fields.length < 2) {
1384
- return ''
1385
- }
1386
-
1387
- let oneof = ''
1388
-
1389
- for (const name of fields) {
1390
- oneof += `
1391
- if (obj.${name} != null) {
1392
- ${fields
1393
- .filter(field => field !== name)
1394
- .map(name => ` obj.${name} = undefined`).join('\n')}
1395
- }
1396
- `
1397
- }
1398
-
1399
- return oneof.trimEnd()
1400
- }).join('\n').trimEnd()
1401
-
1402
- if (oneofs !== '') {
1403
- oneofs = `
1404
- obj = { ...obj }
1405
- ${oneofs}
1406
- `
1407
- }
1408
-
1409
- return oneofs
1410
- }
1411
-
1412
- function createOneOfDecoding (messageDef: ClassDef): string {
1413
- if (messageDef.oneofs == null) {
1414
- return ''
1415
- }
1416
-
1417
- return messageDef.oneofs.map(fields => {
1418
- if (fields.length < 2) {
1419
- return ''
1420
- }
1421
-
1422
- let oneof = ''
1423
-
1424
- for (const name of fields) {
1425
- oneof += `
1426
- if (obj.${name} != null) {
1427
- ${fields
1428
- .filter(field => field !== name)
1429
- .map(name => ` delete obj.${name}`).join('\n')}
1430
- }
1431
- `
1432
- }
1433
-
1434
- return oneof.trimEnd()
1435
- }).join('\n').trimEnd()
1436
- }