| 545 | * @returns {string} |
| 546 | */ |
| 547 | const bestEffortStringify= (payload, spaces= undefined)=> { |
| 548 | const seenSet= new Set(); |
| 549 | const replacer= (_, val)=> { |
| 550 | switch( typeof val){ |
| 551 | case 'object': { |
| 552 | if( val=== null) { |
| 553 | return null; |
| 554 | } |
| 555 | if( setHas(seenSet, val)) { |
| 556 | return '[Seen]'; |
| 557 | } |
| 558 | setAdd(seenSet, val); |
| 559 | if( isError(val)) { |
| 560 | return `[${val.name}: ${val.message}]`; |
| 561 | } |
| 562 | if( toStringTagSymbol in val) { |
| 563 | // For the built-ins that have or inherit a `Symbol.toStringTag`-named |
| 564 | // property, most of them inherit the default `toString` method, |
| 565 | // which will print in a similar manner: `"[object Foo]"` vs |
| 566 | // `"[Foo]"`. The exceptions are |
| 567 | // * `Symbol.prototype`, `BigInt.prototype`, `String.prototype` |
| 568 | // which don't matter to us since we handle primitives |
| 569 | // separately and we don't care about primitive wrapper objects. |
| 570 | // * TODO |
| 571 | // `Date.prototype`, `TypedArray.prototype`. |
| 572 | // Hmmm, we probably should make special cases for these. We're |
| 573 | // not using these yet, so it's not urgent. But others will run |
| 574 | // into these. |
| 575 | // |
| 576 | // Once #2018 is closed, the only objects in our code that have or |
| 577 | // inherit a `Symbol.toStringTag`-named property are remotables |
| 578 | // or their remote presences. |
| 579 | // This printing will do a good job for these without |
| 580 | // violating abstraction layering. This behavior makes sense |
| 581 | // purely in terms of JavaScript concepts. That's some of the |
| 582 | // motivation for choosing that representation of remotables |
| 583 | // and their remote presences in the first place. |
| 584 | return `[${val[toStringTagSymbol]}]`; |
| 585 | } |
| 586 | if( isArray(val)) { |
| 587 | return val; |
| 588 | } |
| 589 | const names= keys(val); |
| 590 | if( names.length< 2) { |
| 591 | return val; |
| 592 | } |
| 593 | let sorted= true; |
| 594 | for( let i= 1; i< names.length; i+= 1) { |
| 595 | if( names[i- 1]>= names[i]) { |
| 596 | sorted= false; |
| 597 | break; |
| 598 | } |
| 599 | } |
| 600 | if( sorted) { |
| 601 | return val; |
| 602 | } |
| 603 | arraySort(names); |
| 604 | const entries= arrayMap(names, (name)=>[name, val[name]]); |