@aircast-4g/mavlink 1.1.7 → 1.1.9

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.
@@ -8,6 +8,34 @@ function isTestTypes(msg) {
8
8
 
9
9
  // Auto-generated decoder and parser for test dialect
10
10
  // Generated from MAVLink XML definitions
11
+ // Embedded MAVLink CRC implementation
12
+ const CRC_EXTRA = {
13
+ 17000: 0
14
+ };
15
+ class MAVLinkCRC {
16
+ static calculate(data, crcExtra) {
17
+ let crc = 0xffff;
18
+ // Process all message bytes using MCRF4XX algorithm
19
+ for (let i = 0; i < data.length; i++) {
20
+ let tmp = data[i] ^ (crc & 0xff);
21
+ tmp = (tmp ^ (tmp << 4)) & 0xff;
22
+ crc = ((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4)) & 0xffff;
23
+ }
24
+ // Add CRC_EXTRA byte using the same algorithm
25
+ let tmp = crcExtra ^ (crc & 0xff);
26
+ tmp = (tmp ^ (tmp << 4)) & 0xff;
27
+ crc = ((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4)) & 0xffff;
28
+ return crc;
29
+ }
30
+ static validate(data, messageId, receivedChecksum) {
31
+ const crcExtra = CRC_EXTRA[messageId];
32
+ if (crcExtra === undefined) {
33
+ return false;
34
+ }
35
+ const calculatedChecksum = this.calculate(data, crcExtra);
36
+ return calculatedChecksum === receivedChecksum;
37
+ }
38
+ }
11
39
  // Base dialect parser class
12
40
  class DialectParser {
13
41
  constructor(dialectName) {
@@ -95,7 +123,9 @@ class DialectParser {
95
123
  frameOffset += 13;
96
124
  }
97
125
  }
98
- frame.crc_ok = true; // Simplified - not doing CRC validation
126
+ // Validate CRC using proper MAVLink algorithm
127
+ const headerAndPayload = data.slice(offset + 1, offset + frameOffset - offset - 2);
128
+ frame.crc_ok = MAVLinkCRC.validate(headerAndPayload, frame.message_id, frame.checksum);
99
129
  frame.protocol_version = isV2 ? 2 : 1;
100
130
  return { frame: frame, bytesConsumed: frameOffset - offset };
101
131
  }
@@ -186,13 +216,30 @@ class DialectParser {
186
216
  const isArray = field.arrayLength !== undefined;
187
217
  const arrayLength = field.arrayLength || 1;
188
218
  if (isArray && arrayLength > 1) {
189
- const values = [];
190
- let totalBytes = 0;
191
219
  // Strip array notation from type to avoid double processing
192
220
  let baseType = field.type;
193
221
  if (baseType.includes('[') && baseType.includes(']')) {
194
222
  baseType = baseType.substring(0, baseType.indexOf('['));
195
223
  }
224
+ // Special handling for char arrays - return as string
225
+ if (baseType === 'char') {
226
+ const chars = [];
227
+ let totalBytes = 0;
228
+ for (let i = 0; i < arrayLength; i++) {
229
+ if (offset + totalBytes >= view.byteLength)
230
+ break;
231
+ const charCode = view.getUint8(offset + totalBytes);
232
+ if (charCode === 0)
233
+ break; // Null terminator
234
+ chars.push(String.fromCharCode(charCode));
235
+ totalBytes += 1;
236
+ }
237
+ // Return string value and total bytes for the array
238
+ return { value: chars.join(''), bytesRead: arrayLength }; // Always consume full array size
239
+ }
240
+ // Handle other array types
241
+ const values = [];
242
+ let totalBytes = 0;
196
243
  for (let i = 0; i < arrayLength; i++) {
197
244
  if (offset + totalBytes >= view.byteLength)
198
245
  break;
@@ -282,6 +329,347 @@ class DialectParser {
282
329
  supportsMessage(messageId) {
283
330
  return this.messageDefinitions.has(messageId);
284
331
  }
332
+ // Serialization methods for outgoing commands
333
+ serializeMessage(message) {
334
+ const messageDef = Array.from(this.messageDefinitions.values())
335
+ .find(def => def.name === message.message_name);
336
+ if (!messageDef) {
337
+ throw new Error(`Unknown message type: ${message.message_name}`);
338
+ }
339
+ // Extract fields from either flat structure or payload structure
340
+ const messageFields = this.extractMessageFields(message, messageDef.fields);
341
+ // Complete the message with all defined fields (including extension fields with defaults)
342
+ const completeMessage = this.completeMessageWithDefaults(messageFields, messageDef.fields);
343
+ const payload = this.serializePayload(completeMessage, messageDef.fields);
344
+ return this.createMAVLinkFrame(message, messageDef.id, payload);
345
+ }
346
+ // Extract message fields from payload structure (payload format required)
347
+ extractMessageFields(message, fieldDefinitions) {
348
+ // Require payload structure
349
+ if (!message.payload || typeof message.payload !== 'object') {
350
+ throw new Error(`Message must have a 'payload' object containing the message fields. Expected format: { message_name: '...', system_id: 1, component_id: 1, sequence: 0, payload: { ...fields } }`);
351
+ }
352
+ return message.payload;
353
+ }
354
+ // Helper method to complete message with all defined fields
355
+ completeMessageWithDefaults(message, fields) {
356
+ const completeMessage = { ...message };
357
+ for (const field of fields) {
358
+ if (completeMessage[field.name] === undefined) {
359
+ completeMessage[field.name] = this.getDefaultValueForField(field);
360
+ }
361
+ }
362
+ return completeMessage;
363
+ }
364
+ // Get default value for a field based on its definition
365
+ getDefaultValueForField(field) {
366
+ const isArray = field.arrayLength !== undefined && field.arrayLength > 1;
367
+ if (isArray) {
368
+ return [];
369
+ }
370
+ let baseType = field.type;
371
+ if (baseType.includes('[') && baseType.includes(']')) {
372
+ baseType = baseType.substring(0, baseType.indexOf('['));
373
+ }
374
+ switch (baseType) {
375
+ case 'uint8_t':
376
+ case 'int8_t':
377
+ case 'uint16_t':
378
+ case 'int16_t':
379
+ case 'uint32_t':
380
+ case 'int32_t':
381
+ case 'float':
382
+ case 'double':
383
+ return 0;
384
+ case 'uint64_t':
385
+ case 'int64_t':
386
+ return 0n;
387
+ case 'char':
388
+ return field.type.includes('[') ? '' : '\0';
389
+ default:
390
+ return 0;
391
+ }
392
+ }
393
+ serializePayload(message, fields) {
394
+ // Calculate total payload size
395
+ let totalSize = 0;
396
+ for (const field of fields) {
397
+ totalSize += this.getFieldSize(field);
398
+ }
399
+ const buffer = new ArrayBuffer(totalSize);
400
+ const view = new DataView(buffer);
401
+ let offset = 0;
402
+ for (const field of fields) {
403
+ const value = message[field.name];
404
+ const bytesWritten = this.serializeField(view, offset, field, value);
405
+ offset += bytesWritten;
406
+ }
407
+ // Implement MAVLink payload trimming: remove trailing zero bytes from extension fields only
408
+ // This is required for proper handling of extension fields
409
+ const fullPayload = new Uint8Array(buffer);
410
+ // Calculate minimum payload size (core fields only)
411
+ let corePayloadSize = 0;
412
+ let extensionStartOffset = 0;
413
+ let hasExtensions = false;
414
+ message.message_name;
415
+ for (const field of fields) {
416
+ const fieldSize = this.getFieldSize(field);
417
+ // Check if this is an extension field using proper XML-based detection
418
+ const isExtensionField = field.extension === true;
419
+ if (isExtensionField) {
420
+ if (!hasExtensions) {
421
+ extensionStartOffset = corePayloadSize;
422
+ hasExtensions = true;
423
+ }
424
+ // Don't add extension field sizes to core payload size
425
+ }
426
+ else {
427
+ // This is a core field - always add to core payload size
428
+ corePayloadSize += fieldSize;
429
+ }
430
+ }
431
+ // If there are no extension fields, don't trim at all
432
+ if (!hasExtensions) {
433
+ return fullPayload;
434
+ }
435
+ // Find the last non-zero byte in extension fields only
436
+ let trimmedLength = fullPayload.length;
437
+ // If we have extensions, check if they contain any non-zero bytes
438
+ if (hasExtensions && extensionStartOffset < fullPayload.length) {
439
+ // Look for non-zero bytes in the extension section
440
+ let hasNonZeroExtensions = false;
441
+ for (let i = extensionStartOffset; i < fullPayload.length; i++) {
442
+ if (fullPayload[i] !== 0) {
443
+ hasNonZeroExtensions = true;
444
+ break;
445
+ }
446
+ }
447
+ if (!hasNonZeroExtensions) {
448
+ // All extension fields are zero, trim them all
449
+ trimmedLength = corePayloadSize;
450
+ }
451
+ else {
452
+ // Find the last non-zero byte in extension fields
453
+ for (let i = fullPayload.length - 1; i >= extensionStartOffset; i--) {
454
+ if (fullPayload[i] !== 0) {
455
+ trimmedLength = i + 1;
456
+ break;
457
+ }
458
+ }
459
+ }
460
+ }
461
+ // Never trim below the core payload size
462
+ if (trimmedLength < corePayloadSize) {
463
+ trimmedLength = corePayloadSize;
464
+ }
465
+ // Return trimmed payload if it's shorter than the original
466
+ if (trimmedLength < fullPayload.length) {
467
+ return fullPayload.slice(0, trimmedLength);
468
+ }
469
+ return fullPayload;
470
+ }
471
+ serializeField(view, offset, field, value) {
472
+ const isArray = field.arrayLength !== undefined;
473
+ const arrayLength = field.arrayLength || 1;
474
+ if (isArray && arrayLength > 1) {
475
+ let totalBytes = 0;
476
+ let baseType = field.type;
477
+ if (baseType.includes('[') && baseType.includes(']')) {
478
+ baseType = baseType.substring(0, baseType.indexOf('['));
479
+ }
480
+ // Special handling for char arrays - treat string as char array
481
+ if (baseType === 'char' && typeof value === 'string') {
482
+ const str = value;
483
+ for (let i = 0; i < arrayLength; i++) {
484
+ const charCode = i < str.length ? str.charCodeAt(i) : 0;
485
+ view.setUint8(offset + totalBytes, charCode);
486
+ totalBytes += 1;
487
+ }
488
+ return totalBytes;
489
+ }
490
+ // Handle other array types
491
+ const arrayValue = Array.isArray(value) ? value : [value];
492
+ for (let i = 0; i < arrayLength; i++) {
493
+ const itemValue = i < arrayValue.length ? arrayValue[i] : this.getDefaultValueForType(baseType);
494
+ const bytesWritten = this.serializeSingleValue(view, offset + totalBytes, baseType, itemValue);
495
+ totalBytes += bytesWritten;
496
+ }
497
+ return totalBytes;
498
+ }
499
+ else {
500
+ return this.serializeSingleValue(view, offset, field.type, value);
501
+ }
502
+ }
503
+ serializeSingleValue(view, offset, type, value) {
504
+ const actualValue = value ?? this.getDefaultValueForType(type);
505
+ switch (type) {
506
+ case 'uint8_t':
507
+ view.setUint8(offset, Number(actualValue));
508
+ return 1;
509
+ case 'int8_t':
510
+ view.setInt8(offset, Number(actualValue));
511
+ return 1;
512
+ case 'uint16_t':
513
+ view.setUint16(offset, Number(actualValue), true);
514
+ return 2;
515
+ case 'int16_t':
516
+ view.setInt16(offset, Number(actualValue), true);
517
+ return 2;
518
+ case 'uint32_t':
519
+ view.setUint32(offset, Number(actualValue), true);
520
+ return 4;
521
+ case 'int32_t':
522
+ view.setInt32(offset, Number(actualValue), true);
523
+ return 4;
524
+ case 'uint64_t':
525
+ view.setBigUint64(offset, typeof actualValue === 'bigint' ? actualValue : BigInt(Number(actualValue) || 0), true);
526
+ return 8;
527
+ case 'int64_t':
528
+ view.setBigInt64(offset, typeof actualValue === 'bigint' ? actualValue : BigInt(Number(actualValue) || 0), true);
529
+ return 8;
530
+ case 'float':
531
+ view.setFloat32(offset, Number(actualValue), true);
532
+ return 4;
533
+ case 'double':
534
+ view.setFloat64(offset, Number(actualValue), true);
535
+ return 8;
536
+ case 'char':
537
+ view.setUint8(offset, typeof actualValue === 'string' ? actualValue.charCodeAt(0) : Number(actualValue));
538
+ return 1;
539
+ default:
540
+ if (type.startsWith('char[') && type.endsWith(']')) {
541
+ const length = parseInt(type.slice(5, -1));
542
+ const str = String(actualValue);
543
+ for (let i = 0; i < length; i++) {
544
+ const charCode = i < str.length ? str.charCodeAt(i) : 0;
545
+ view.setUint8(offset + i, charCode);
546
+ }
547
+ return length;
548
+ }
549
+ view.setUint8(offset, Number(actualValue));
550
+ return 1;
551
+ }
552
+ }
553
+ getFieldSize(field) {
554
+ if (typeof field === 'string') {
555
+ // Legacy support for type string
556
+ if (field.includes('[') && field.includes(']')) {
557
+ const baseType = field.substring(0, field.indexOf('['));
558
+ const arrayLength = parseInt(field.substring(field.indexOf('[') + 1, field.indexOf(']')));
559
+ return this.getSingleFieldSize(baseType) * arrayLength;
560
+ }
561
+ return this.getSingleFieldSize(field);
562
+ }
563
+ // Handle FieldDefinition object
564
+ const type = field.type;
565
+ const arrayLength = field.arrayLength;
566
+ if (arrayLength && arrayLength > 1) {
567
+ return this.getSingleFieldSize(type) * arrayLength;
568
+ }
569
+ if (type.includes('[') && type.includes(']')) {
570
+ const baseType = type.substring(0, type.indexOf('['));
571
+ const parsedArrayLength = parseInt(type.substring(type.indexOf('[') + 1, type.indexOf(']')));
572
+ return this.getSingleFieldSize(baseType) * parsedArrayLength;
573
+ }
574
+ return this.getSingleFieldSize(type);
575
+ }
576
+ getSingleFieldSize(type) {
577
+ switch (type) {
578
+ case 'uint8_t':
579
+ case 'int8_t':
580
+ case 'char':
581
+ return 1;
582
+ case 'uint16_t':
583
+ case 'int16_t':
584
+ return 2;
585
+ case 'uint32_t':
586
+ case 'int32_t':
587
+ case 'float':
588
+ return 4;
589
+ case 'uint64_t':
590
+ case 'int64_t':
591
+ case 'double':
592
+ return 8;
593
+ default:
594
+ return 1;
595
+ }
596
+ }
597
+ getDefaultValueForType(type) {
598
+ switch (type) {
599
+ case 'uint8_t':
600
+ case 'int8_t':
601
+ case 'uint16_t':
602
+ case 'int16_t':
603
+ case 'uint32_t':
604
+ case 'int32_t':
605
+ case 'float':
606
+ case 'double':
607
+ return 0;
608
+ case 'uint64_t':
609
+ case 'int64_t':
610
+ return 0n;
611
+ case 'char':
612
+ return 0;
613
+ default:
614
+ return 0;
615
+ }
616
+ }
617
+ createMAVLinkFrame(message, messageId, payload) {
618
+ const systemId = typeof message.system_id === 'number' ? message.system_id : 1;
619
+ const componentId = typeof message.component_id === 'number' ? message.component_id : 1;
620
+ const sequence = typeof message.sequence === 'number' ? message.sequence : 0;
621
+ // Automatically determine protocol version based on message requirements
622
+ const needsV2 = messageId > 255; // v1 only supports 8-bit message IDs (0-255)
623
+ const userSpecifiedVersion = typeof message.protocol_version === 'number' ? message.protocol_version : null;
624
+ // Use user-specified version if provided, otherwise auto-detect
625
+ const protocolVersion = userSpecifiedVersion !== null ? userSpecifiedVersion : (needsV2 ? 2 : 1);
626
+ const isV2 = protocolVersion === 2;
627
+ const magic = isV2 ? 0xFD : 0xFE;
628
+ // Calculate frame size based on protocol version
629
+ const headerSize = isV2 ? 10 : 6; // v2 has extra fields
630
+ const frameSize = headerSize + payload.length + 2;
631
+ const buffer = new ArrayBuffer(frameSize);
632
+ const view = new DataView(buffer);
633
+ let offset = 0;
634
+ // Header
635
+ view.setUint8(offset++, magic);
636
+ view.setUint8(offset++, payload.length);
637
+ if (isV2) {
638
+ // MAVLink v2: magic(1) + len(1) + incompat_flags(1) + compat_flags(1) + seq(1) + sysid(1) + compid(1) + msgid(3) + payload + checksum(2)
639
+ view.setUint8(offset++, 0); // incompat_flags
640
+ view.setUint8(offset++, 0); // compat_flags
641
+ view.setUint8(offset++, sequence);
642
+ view.setUint8(offset++, systemId);
643
+ view.setUint8(offset++, componentId);
644
+ // 24-bit message ID in v2
645
+ view.setUint8(offset++, messageId & 0xFF);
646
+ view.setUint8(offset++, (messageId >> 8) & 0xFF);
647
+ view.setUint8(offset++, (messageId >> 16) & 0xFF);
648
+ }
649
+ else {
650
+ // MAVLink v1: magic(1) + len(1) + seq(1) + sysid(1) + compid(1) + msgid(1) + payload + checksum(2)
651
+ view.setUint8(offset++, sequence);
652
+ view.setUint8(offset++, systemId);
653
+ view.setUint8(offset++, componentId);
654
+ view.setUint8(offset++, messageId & 0xFF); // 8-bit message ID in v1
655
+ }
656
+ // Payload
657
+ const payloadView = new Uint8Array(buffer, offset, payload.length);
658
+ payloadView.set(payload);
659
+ offset += payload.length;
660
+ // Calculate proper MAVLink CRC with CRC_EXTRA
661
+ const crcExtra = CRC_EXTRA[messageId];
662
+ if (crcExtra === undefined) {
663
+ throw new Error("No CRC_EXTRA defined for message ID " + messageId);
664
+ }
665
+ // Get message data (exclude start byte and checksum)
666
+ const messageData = new Uint8Array(buffer, 1, offset - 1);
667
+ const checksum = MAVLinkCRC.calculate(messageData, crcExtra);
668
+ // Checksum (little endian)
669
+ view.setUint8(offset++, checksum & 0xFF);
670
+ view.setUint8(offset++, (checksum >> 8) & 0xFF);
671
+ return new Uint8Array(buffer);
672
+ }
285
673
  }
286
674
  const MESSAGE_DEFINITIONS = [
287
675
  {
@@ -289,102 +677,102 @@ const MESSAGE_DEFINITIONS = [
289
677
  name: 'TEST_TYPES',
290
678
  fields: [
291
679
  {
292
- name: 'c',
293
- type: 'char',
294
- },
295
- {
296
- name: 's',
297
- type: 'char',
298
- arrayLength: 10,
680
+ name: 'u64',
681
+ type: 'uint64_t',
299
682
  },
300
683
  {
301
- name: 'u8',
302
- type: 'uint8_t',
684
+ name: 's64',
685
+ type: 'int64_t',
303
686
  },
304
687
  {
305
- name: 'u16',
306
- type: 'uint16_t',
688
+ name: 'd',
689
+ type: 'double',
307
690
  },
308
691
  {
309
- name: 'u32',
310
- type: 'uint32_t',
692
+ name: 'u64_array',
693
+ type: 'uint64_t',
694
+ arrayLength: 3,
311
695
  },
312
696
  {
313
- name: 'u64',
314
- type: 'uint64_t',
697
+ name: 's64_array',
698
+ type: 'int64_t',
699
+ arrayLength: 3,
315
700
  },
316
701
  {
317
- name: 's8',
318
- type: 'int8_t',
702
+ name: 'd_array',
703
+ type: 'double',
704
+ arrayLength: 3,
319
705
  },
320
706
  {
321
- name: 's16',
322
- type: 'int16_t',
707
+ name: 'u32',
708
+ type: 'uint32_t',
323
709
  },
324
710
  {
325
711
  name: 's32',
326
712
  type: 'int32_t',
327
713
  },
328
- {
329
- name: 's64',
330
- type: 'int64_t',
331
- },
332
714
  {
333
715
  name: 'f',
334
716
  type: 'float',
335
717
  },
336
718
  {
337
- name: 'd',
338
- type: 'double',
719
+ name: 'u32_array',
720
+ type: 'uint32_t',
721
+ arrayLength: 3,
339
722
  },
340
723
  {
341
- name: 'u8Array',
342
- type: 'uint8_t',
724
+ name: 's32_array',
725
+ type: 'int32_t',
343
726
  arrayLength: 3,
344
727
  },
345
728
  {
346
- name: 'u16Array',
347
- type: 'uint16_t',
729
+ name: 'f_array',
730
+ type: 'float',
348
731
  arrayLength: 3,
349
732
  },
350
733
  {
351
- name: 'u32Array',
352
- type: 'uint32_t',
353
- arrayLength: 3,
734
+ name: 'u16',
735
+ type: 'uint16_t',
354
736
  },
355
737
  {
356
- name: 'u64Array',
357
- type: 'uint64_t',
358
- arrayLength: 3,
738
+ name: 's16',
739
+ type: 'int16_t',
359
740
  },
360
741
  {
361
- name: 's8Array',
362
- type: 'int8_t',
742
+ name: 'u16_array',
743
+ type: 'uint16_t',
363
744
  arrayLength: 3,
364
745
  },
365
746
  {
366
- name: 's16Array',
747
+ name: 's16_array',
367
748
  type: 'int16_t',
368
749
  arrayLength: 3,
369
750
  },
370
751
  {
371
- name: 's32Array',
372
- type: 'int32_t',
373
- arrayLength: 3,
752
+ name: 'c',
753
+ type: 'char',
374
754
  },
375
755
  {
376
- name: 's64Array',
377
- type: 'int64_t',
378
- arrayLength: 3,
756
+ name: 's',
757
+ type: 'char',
758
+ arrayLength: 10,
379
759
  },
380
760
  {
381
- name: 'fArray',
382
- type: 'float',
761
+ name: 'u8',
762
+ type: 'uint8_t',
763
+ },
764
+ {
765
+ name: 's8',
766
+ type: 'int8_t',
767
+ },
768
+ {
769
+ name: 'u8_array',
770
+ type: 'uint8_t',
383
771
  arrayLength: 3,
384
772
  },
385
773
  {
386
- name: 'dArray',
387
- type: 'double',
774
+ name: 's8_array',
775
+ type: 'int8_t',
388
776
  arrayLength: 3,
389
777
  },
390
778
  ]
@@ -408,6 +796,43 @@ class TestParser extends DialectParser {
408
796
  }
409
797
  }
410
798
  }
799
+ // Dialect-specific serializer
800
+ class TestSerializer {
801
+ constructor() {
802
+ this.parser = new TestParser();
803
+ }
804
+ // Serialize a message to MAVLink bytes
805
+ serialize(message) {
806
+ return this.parser.serializeMessage(message);
807
+ }
808
+ // Complete a message with all defined fields (including extension fields with defaults)
809
+ // This is useful to see what the serializer would send without actually serializing
810
+ // Requires payload structure format
811
+ completeMessage(message) {
812
+ const messageDef = Array.from(this.parser['messageDefinitions'].values())
813
+ .find(def => def.name === message.message_name);
814
+ if (!messageDef) {
815
+ throw new Error(`Unknown message type: ${message.message_name}`);
816
+ }
817
+ // Extract fields from payload structure (throws error if not payload format)
818
+ const messageFields = this.parser['extractMessageFields'](message, messageDef.fields);
819
+ // Complete the message with defaults
820
+ const completedFields = this.parser['completeMessageWithDefaults'](messageFields, messageDef.fields);
821
+ // Return in the payload structure format
822
+ return {
823
+ ...message,
824
+ payload: completedFields
825
+ };
826
+ }
827
+ // Get supported message names for this dialect
828
+ getSupportedMessages() {
829
+ return Array.from(this.parser['messageDefinitions'].values()).map(def => def.name);
830
+ }
831
+ // Check if a message is supported by this dialect
832
+ supportsMessage(messageName) {
833
+ return Array.from(this.parser['messageDefinitions'].values()).some(def => def.name === messageName);
834
+ }
835
+ }
411
836
 
412
- export { TestParser, isTestTypes };
837
+ export { TestParser, TestSerializer, isTestTypes };
413
838
  //# sourceMappingURL=index.js.map