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