{"_id":"stagnant","_rev":"79-2342f4dc01df3e437a3e576bccb1050f","name":"stagnant","dist-tags":{"latest":"0.0.33","next":"0.0.34-next.52"},"versions":{"0.0.0":{"name":"stagnant","version":"0.0.0","description":"Measure your slow code, make it _fast_.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/polyfill":"^7.12.1","@babel/preset-env":"^7.14.4","@babel/preset-stage-2":"^7.8.3","eslint":"^7.27.0","nodemon":"^2.0.7"},"gitHead":"bcb29bb90b46125ad15e42a8b427b84d49a91d37","_id":"stagnant@0.0.0","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-LOpG4UydsqOYx1dd8D+C7JPsLVWUqINz2tHCTDTKuHhn9guFhocWHm44aLvy1tssjbARbmy8xYS3YgpEOWG07g==","shasum":"e2b281b0bd0d98b6dcd4c655211087bf3521140c","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.0.tgz","fileCount":5,"unpackedSize":13777,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgteSwCRA9TVsSAnZWagAA36YP/3hHzpyyWu0MvKUTmLpH\nVOtBwhvkHKRdamB/imKot7858gkDqOpZhK/xpzYygFGi/RdsJlmGnYP1UsUK\nAmskOkJR0DG0fvYDSyXGNMv0ncE89kcw5rorqUOLOkC1owWXnaKLAnH1+CKD\nC5KIuAe+xq7T2sPhMNiedGxeW9K7DN+2Ma8jczdmdKq75Z/LYh67m4j6uXMJ\nUXwMpKiHJDYO9eKFMAeqzm+2EcGuvzyfzlzBbso7AID7oLqbsBRVhNa26iw7\nbNYtmULDGmP43owfkLtK3pVLzeN4AQhEXPlGo+Y1ijGuBE/ADAJVdY1xlPWV\nfUsad3Qs4JNwoBMwno4uPhXKcVCLzqUKUh0m3u/K7CewoVpb+hxJCzaTcXXO\n8jOIkcKu8+tkXz+Ik4XHwWzIoRKCSRGvrmHK8cSmqoqLTEJTZJ4zFfwnHjG2\nh2+WmamGrwTEUsvPgiwql0wnDt6tqEHEoHhW5lr6vHgqpzqdIjHBBOfeVGC2\nHFNSWfjPVwdqr9VkftBcN/oSRYQZGPB297HVe/IqCA+T5r5Wxcpt9Sypx5P0\nvw466rHMck3N1q3Z03ZslezpXrX7k0WPdPLuWc+HlxjNmAT261kLgPZlaglB\nBNygjuqSHBvoZqiBcgV6AUnkVKSTGS/RWmqnvVtHiIG3vi3+jb2D+NhRX4Sb\n0qqh\r\n=0ADY\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICsxCiuWXYhQ70NubSbr62uLdd9szvhwaNAynl78CF5PAiBgm7wDJBPk+H9iLOLEeoyVUpI9kuixZPIGKpKBnNLcGA=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.0_1622533296306_0.658478722179884"},"_hasShrinkwrap":false},"0.0.1":{"name":"stagnant","version":"0.0.1","description":"Measure your slow code, make it _fast_.","main":"index.js","type":"module","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/polyfill":"^7.12.1","@babel/preset-env":"^7.14.4","@babel/preset-stage-2":"^7.8.3","dotenv":"^10.0.0","eslint":"^7.27.0","libhoney":"^2.3.0","node-fetch":"^2.6.1","nodemon":"^2.0.7"},"gitHead":"f0455b984168066a2174eeb26481e6ce24ead7df","_id":"stagnant@0.0.1","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-LQPAceE70sJu/Rqn17I+NC3yH9JDYVUpU4hFFAzFFSh0k/06jiXwb4FNjH9KVbSsrKkmUImnelDxprbpKO92DQ==","shasum":"e8cd368264c2202acb881c16a7b1d2423814c87f","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.1.tgz","fileCount":9,"unpackedSize":19960,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtjWhCRA9TVsSAnZWagAArG4QAJjzDU+YMhrinMiL9Of5\nAbkFuCfKn+JnA7L4x++REQRFi9CJVrwqJ4yPeOcASZK82Ren22LIcfLgAMj8\nCQrPf60VTNUp5x+AERuYWBFseu2rYs47AsEnpYDkqzEAPyHNdeFprC8+mqH+\n5yDo5IrkwSUcgHyTaxcLOmq8dGwEKlvDPkM3myhXN9pAZkZLu6JDP17AVdFG\nbYl/EqRSZNHBlfM4DU/B48Y9MemDl6J+KzVu/5QnimWAVj2dYAMewtgJdojv\npAAvAqPF8psepwEABnvmsMQc+Utys0OPO+gn3yOwm+UXfrHFcd78sTVzcuX7\nhO4Y+PoXzv2luGoxWrznoYuPrr2GWW3sx7XuB1pOQrlnr1yZJyWyjB9dxpi4\nCPNSN7oYBb9yR4+FqFq4/eXJV624XQa7A7iARx7kWl6XhZEwDJqKoJaL/MKL\nKWaLcmMFCGypp9GD9Fv2iXnBKq2/9v/opg8j5nPvQwd3UbZ8Qgvo/IY7VJc/\nLAkcyEJyEPXymVlpbNGM4ZRGMmxvEJstW2cpTOaIOd2HzvGlxEvWG0JQf7HY\nFksMOR2Lv3lW8lIRfHhfgtg5WBMQICtPlFrFPP5Dlevm8SA6Z/MvD52z9KsD\n6J1QbnW0yvKRs87nhFcP+jvTkSR/ZMe0QaGs3pk8tacFOKpl7pq/C4OnPvPY\nQC7r\r\n=AOAb\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDJLFdsxq0dvEFontgIyMIYP2bcU+lJ39cDBgqS4q0qNAIhAMvGOit20krG6IqplkXB6W3iudz8l6MkzAe9e3EzElMy"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.1_1622554016640_0.7563331158257649"},"_hasShrinkwrap":false},"0.0.2":{"name":"stagnant","version":"0.0.2","description":"Measure your slow code, make it _fast_.","main":"index.js","type":"module","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/polyfill":"^7.12.1","@babel/preset-env":"^7.14.4","@babel/preset-stage-2":"^7.8.3","dotenv":"^10.0.0","eslint":"^7.27.0","libhoney":"^2.3.0","node-fetch":"^2.6.1","nodemon":"^2.0.7"},"gitHead":"f2f14b53a9255aa41b80f464771288e9344dc85b","_id":"stagnant@0.0.2","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-7owGnM6br8vK+s/GN7yClCMcLT/xL5pLacXnznzS33cHlwqRXV0PKo4CYcH1jXXDznM5ptJDEoZI/f4UWqnQvA==","shasum":"10cad75578279ff3f64d88122729805496c68cac","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.2.tgz","fileCount":10,"unpackedSize":51958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtrZHCRA9TVsSAnZWagAAkZ4P/Rjj6BYb9+1wVSIzq/w9\ncp4JhVyNA9Br7b4n8mQRq0hgJnuhisEDnIne+5+zFP1oo7YeVBAT4GJYq+JC\ncGeZhY5dAZqHCZsxjySZhbWA+bISHUFIOf+QL7MzS0d6E0A7UBCvuaAK72nh\n21mKmGPD/Ei12CAsb0aMuDaTXPKd7M9tlCWGmUeD3LflUxdCuRE4I1dGGbJk\n4cN5nohh4bxiMpc349JfRShqY28IQUJnaNRUSSW6hrNSJB95JXGSZ+gwGeRr\n7HK96nul8ddzQ5l8vQH1CPdJXlCEdTcWOrPNHLr028rH71KtWDTITOmCbR36\nu+gY6y35my1UU87i/rAHveFZsItR43vaFeIM/d6AkrdzF1NZ1We/+bZ8TGQs\nYAJohCrj7dKBh4+qFV4JxFgzdnNplwEHUgg9agkP6AiHM9kZ5BBY+g37FXDk\n2bbEYNBeDvoxHOsegUlgybfDvviy7HeognAxet1vr+RmKGXA6P8eIVDA39qw\nVZg33L/cGtwui1PdexCiWfQvJT3hflNFtO1NltXFTZAvKrgJCLTiwnIEg+vR\nsXJL+Cui0DHOv93xwKBEFACfw3sJizdl1Yg7RZXnRoTGbpa6VMVh8ee5qoNW\nRspypzcZSsaSeiIP4XuDHsOdhZX9C7CDYs/rpMiSEWqDXCNkTM8Hon5K7TT7\nxHsD\r\n=X4t1\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICAV+feTpwRbIi6BTlWoe93TBL9c54U1+Kilmsz9vW/nAiEA0Cv6jdKGsjS0FS0DeNNM1foG+JHwZ8q9fY7VeinXfjU="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.2_1622586950587_0.046212707442229606"},"_hasShrinkwrap":false},"0.0.3":{"name":"stagnant","version":"0.0.3","description":"Measure your slow code, make it _fast_.","main":"dist/stagnant.umd.js","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"gitHead":"27f8ca106d9726f6d8c9a39d31da2a2951b0f637","_id":"stagnant@0.0.3","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-Skapto5qG3VxqqFBaQiKIY8BfRkiRYOLBuwuCDQKGqVJkBIqF7HEoJaFOQ5wUNhA5tZ5lqA8FzV4BxhpZPjfmg==","shasum":"3dcc4a990dc178770e3a37b4d3a376918d8c88d6","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.3.tgz","fileCount":15,"unpackedSize":76306,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtsWOCRA9TVsSAnZWagAA2swP/RwBtlxOGsuvCo1hubst\npalveivgahC3fYk59HewEgJmJpjhqwrWGYSwMRVDB8UzSFUZeCX4n01z/cuH\nElXO8hGQgdMdXv5usZubkviBhhM1e1p67URF7OSzhbdNEcSqOWJaCtE/aHAg\n6TVr+DGlpeoiA8hXhx4nGPQss44inesXuU6KdpeeY1u+DNbFKKJtkIcExdFZ\nCZ43m3gqI7MQP2WeDMYMrjvzrhdsqVDpRnNK0xPshlgy7omSxJ4GVmhEP3Ia\nq9PBSNNzht9bwpX19pTk0/GTaDzNGdovWFgtdf5fxOfXdvgelKHaxi2Q2k4t\n/ryYit/voCRiQA26uArsLhtHwo06i7tIgLi+iGSNAGvxer365Air8/vQ3nJy\n/ARZyjoUIkCSLclkROJTmOMwmhUvSC8oA/WQ10afi7ZkoRdBEwG1sG9qQO/Q\nDFca0qUPkT7RvMTGDv//uO3W7rv0yDto8nTW6XKIgCCLlFtQxo80ppnYMMxr\noJHEKWcFU71XIPtJRaboeVXgsnzFvazxOETM01v5kt14BJk7F72UZMoD6Zc7\nUnSsS17kz79P5JG0GiJu+x77RZFKUa82DwSt49FUa7KfE/xmaGQzQhOZS+k2\nm5QcjSbGamL4kq7H+NMwKvDxOK3x4wm4hsa9wMiU5lPSjLy7ql324YblVddF\nPFEi\r\n=GCvM\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCAgrZ2HyvSuJBZkNE143cNsWijZNt6B2A6bz9NJkOG2wIgYbbDF4wJyrOTCF3PvwmRlsC+ICdW4/DkVwb8QUTV5ps="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.3_1622590862375_0.23456272657948718"},"_hasShrinkwrap":false},"0.0.4":{"name":"stagnant","version":"0.0.4","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"gitHead":"94fb896f364025ac3c18f1632278aff0194d7f37","_id":"stagnant@0.0.4","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-crg7LSkUKaaXCelAGBBgDPG4JFqX8FVW5/9IqM/FNNuotdzSYDM9C6HTLi/bwTiTEa7au3qHuXzbawemcpnTuA==","shasum":"e50e4aebaec2f580773363d338756d1c6ce5e28f","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.4.tgz","fileCount":14,"unpackedSize":80888,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtsnkCRA9TVsSAnZWagAAA/IP/3ILAZC+QSlZPOH2FF6W\nvGNXavxsgaLfEvh7GEyQGaokmBvoCw8NXpRpVMthLpJNsh/SKSi0PGmiHO0u\nNK32Hq0rL3dyc4tMY9WXGC9gw+te9gkXOVTdn53rXqnGZRACD9WZ+FBjrVGg\ntGADNzygA02wt6Up3GNvhyyq3cTgPg3zbgaJWbyejpTDEzMOJqIRj6DAdVd0\nBLSzIbxBYD/rkrByMRV0ooPfIAJJvQd9d9CBu0ts/meCvnUTWjSgmS8zH2P+\nOpmW98bDR+dYDqGrJRtLUDnwg4O2CiGBrbPe71aiOfUJ0cbNOEm0esJEsKLh\nxtfNsmbPt2oKMJFgaQs7MM3Gvxo8BI5KvCncSnmBzCurXk8P0k0LTZv7G01G\nh7hztHUx8trxAuv3iwCPyFPkaTFOTeROJ9q5EPCFpKA7VTYF8b1ISQci7RGx\nd8Bwss1805yqoNazg9EGR9QbaJZ4jIPAqiYvGKmkqHb6mqSmeFHE3nyaT/8q\nIJrn/eEbYyqnqZSrnL2RJJtfS6eTO1vmQEqtEd/GjbzNHYd2ZdHQumjnJEm/\nTQmLwTkpqTbYcQ4gGf0HU7N58G2fWdFcS2xqSKU+JB+zNv/yyfMwnv6HaV6N\naRzfV6e+JnTOGYQI+R5650WsfblglSqRqBm1OQ5uIxDknXeiODoYyADGuBZM\nJgts\r\n=B8Lp\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHLAXGS2PapOE1kRMu0oZY5HZZactz1j0kv4LXsVW7hbAiEAozPa5RX+mZX6Djob8zBmYKPSMbjmVNBg8ZTszpeZiTs="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.4_1622591972187_0.9784869871844903"},"_hasShrinkwrap":false},"0.0.5":{"name":"stagnant","version":"0.0.5","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"gitHead":"b2b3c0c8c03f7989be3ff759d524477b8d2a223e","_id":"stagnant@0.0.5","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-IZwk4QS5IsNmmAD1yUjpNm5MElI52AanonEDoYykiwqTC9boOi7/hNU1X/TXiH1bXEtYwX9H+tBaqKwJCgRSpw==","shasum":"d01567d6e29b43787bde4cc8df60861d0bd17b64","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.5.tgz","fileCount":12,"unpackedSize":58949,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtspRCRA9TVsSAnZWagAAbpEQAJxlnigcmo8Zo625rb31\n5pjIznIAc1sg9hMg45GkTwfLHQKfeERRYs8Z5ubcNa0rGH+vFf0at8FfCeWj\npuStnRfIQf5IIv5EJwYcgFX2gmkVdNepB1OtksMFSSrRYycXaMjGAYLwgFbo\nmRORD3Ic8MW5wTmO0l9jjRq8FKeUIiAh8mTh5A7F/cy9EACbOHW6lQtRRdIX\n3b9XjfoT1OKWuUoAjKvr8raQcvPHNBOJjlMubOdp/Q3rU9Ebid3QkkDhlrAH\n2vSWNCFSNRep5+a6yrJMoKImNLUZWoT/Wdxb5eDOQAyVSh+WprnYvv/AWEZq\nvtDKLcn9PWnTh98ZL6dO8V9TdKL7EyL0Ico9kGl9RIZaUM/ck35zgM1eEED3\ntGjW6iCoE9G4d75oYd0PUjIc2fJLJCxyLNKyZEK46gOcdpgQUJrO/SXOoHJD\ncWJOqOL5t4+D8lHvrCErA2+wRu+4aJ6jZT0MN33KLeTgCRaZ3p8wel6FA0qh\ns9WsDopIXC0S1G5PDFqovwVdh2zzcri+6j9mFiQ2oZbL1UiXUdCj/bp1Aygw\niRFTnFClVNLeQGk73CMYiWLAYeF2GJf3OsOCSyTglrCzTq7drQD8TCDCMskH\n1QCO2DIXGXwy4UIYd/UtAZDHZifNlLns4s9+5d+IfIVWZEdh4+gO9JrRO00P\nY5DR\r\n=SroZ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDB75NFnGh8GTYsDBb/d63PTer4uS58VNE5lmh8P9RUNgIhAJkGZN4I8PlA0uzaO55MeKTW3Sq2+2v+geziv6MOewQb"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.5_1622592081252_0.5494534836443763"},"_hasShrinkwrap":false},"0.0.6":{"name":"stagnant","version":"0.0.6","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"e1919eb4f04ed9375bde23ec1710a9e4d946c7f0","_id":"stagnant@0.0.6","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-5PLyN8ai5q7ek1WOEgu7Hk6NtbR75xoC/gp5/dH5MX2qOBdYWVdVRCXU3Ab5NVlLXMJF+mop69p9rCwsZq6Grw==","shasum":"4e61543afd167859a119249dc342de9c6f298092","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.6.tgz","fileCount":12,"unpackedSize":59321,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttDaCRA9TVsSAnZWagAAZtUP/R1nja1SgGv5tEqqjYgl\nUcEoeN8ZBPvq52TOtIid6AOMLlTYwcOJcwhTr0NQP7Ddhw0Bp8tnGOZuVfK3\nKSVxwjjO4LOfw3eQ9zV5JUBRcsNj3rCF29fF+6nq8tEQoAB2sro46Bd6CpEk\nKqDCflPp93K2MZF5e2ftQeYVgh1JCr2I3GaZsZglpRe9nmePYjL7wlNq0XO/\n7fzrdKHPf/JknjAyewPmzkKBMgEgzmz2NHTIkNU/dgmRDNstzBtHa/KLoeF9\nXEdJo9YRsbTrP/IR5wQ1LGX4YaSXOk92QNhF2jbjzwAk96jQ5cEL1fLOdImy\n0IBu5g94sR57M1L/VrbFW49Z4VnUaSurLozwboGzUs40SNKEVBUPuXwWzI8N\nmd75iZ0qECWcCOXDpvuw9/fsKXEy1ZAxyzT36yrLa8SJGfeysL0FjwccA/9D\n5sNu3eCcjLgkJxMxIL1X/2MZZib4VXWL4rKtX1iFfMgDJ+sD4hQmEdADZrty\n+PjL9vbcffpHJAUYXMr5qCaM1yjdaLj0RV+YTIIEuH/b4jE4dL4elkknjfms\nKSC5dT8zG3Os4bB3PXPzoEMosQ3By2crLYGx8oWzeVla73H6s77WTYe0GpdB\nWh5PDdeZGjTB9Isll2k+sWYOlDk2AhpwHi6KasjC1GLZDkE4EeUUdul1XOS1\n98YB\r\n=4V/U\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDajunGC0HsNOATuzGHE1qu/S4M1JEgTYdLmwPfP6FIxAiBspVtOjD3xQowrsr6Ub81SgrUx4YmP0ILAYPT6nw4HWA=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.6_1622593753906_0.39749469317750075"},"_hasShrinkwrap":false},"0.0.7":{"name":"stagnant","version":"0.0.7","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"f2ec896163dbd6b8aeffdb81fd0cc4c784b037a1","_id":"stagnant@0.0.7","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-2JFEhbR0A3aIVc2FpP3asAhQrc5MCWCut8w3nTqXl+ziF0c88qbXP9ihotZz6HaFP4/eI6r8nIacBvFcjfYHeA==","shasum":"f3fb670c03f1c2d111176a19cbe1785654f0d6d6","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.7.tgz","fileCount":12,"unpackedSize":60328,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttKGCRA9TVsSAnZWagAAI0QP/31GOAFcP+o471DkQgWb\n332EqkhLNPqNaQvJHigC7mM4DOXu4LCOGRFWKtHxp4IkXDqjw/bd2iWuvQD/\net4+XUxR9gMrQuE0Xw/txAVB2SiLai9KFVujDNv12W6vej8oeba0CvK18wdu\n924zR5xp5W/3moea00aX8GeFeJ/JujHBxzPjtaYJ+15pC6IL6eRR8EedX3W/\njtc9DEEs2ahymsHC+C5zNfIU0z9ax9p4/LBwcsBUYGQwNyO+sg0gbyldLmbN\nIgJol8er+0TN7XhOxhHQrTcluUO97zE1zm6Fjt7YzZQJUbnpfSysJPpkuRRD\nZv1LaL1tvNcXKjcBWBqL7g7b/YSKZAVQuk7aUdjbJPrbOiF3sz1LpMma8b6B\nPAMzUl0W3RTJ2IynFo6YxMFDqDgzwUPgVIaxCbBvcWIsmFqvcXbQVwSvLIXM\nUI6TgT4o4qh2dcd7+tcy+RBcX1WAP1tx8bag7ozSvmYZBSXZ19QUKTFF2FLt\n84vUZhvmtgr5xhBPqLtKReAJgVrFu+Wq9hEIK4itd0xSXD97oxDZVq0wDbRo\nRUcR1EdRLIOfLH47Z0EEa8GCRlIIwKwKJcKUC6sm9/xEobn4HMH+2qPjXxWo\nDBa1eksS40L7plB11Du0dMin/9W+3lZh/nj6EdPClwxC5ORRTNUq5w3lf8qq\nDuKK\r\n=VfMS\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAyeIccCLOcH77iKUsfZXplKfcmj85Dg87ZD7l7h2jiaAiEApV24LgwpWw0RorNGeb3gAZC80OQj1XFNa5OdFTNbqVM="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.7_1622594182120_0.7535197100471536"},"_hasShrinkwrap":false},"0.0.8":{"name":"stagnant","version":"0.0.8","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"799c645a4b35a5cba00bc4bdef95bc19e4e1060d","_id":"stagnant@0.0.8","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-/Xx551/3nyuYHyf5dnDgHdi9IXtWkPqGvS7cGhK7yS9BzqBBgtSV+Q8tdl6GsmwzTVe6UykzDN1Y8/Ll/s+36w==","shasum":"45f200264f4382266bfdbe06370a18244c3c1094","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.8.tgz","fileCount":14,"unpackedSize":126843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttPFCRA9TVsSAnZWagAAChoP/3KFccM67hF9+0f8i/RV\nmBmcVQ+IlV//yzn86T3fvI0gR8alXQ8T+xs5i87qpLJzNp0V+r7D0Mvgveav\nCwBfySOSpa3OWVxzXCl93wKD6/kuxmGEnCUylaExn6/df4qjcp4NrbXkK9X6\nY8ptspciZcd6G0AeaKKIrEtN7+LSyZm94XWVdLeVo3nZxPeMgazK3hGEOA68\nYMlp5qBYd4RlXke6QM/32XrzpJgnt3pD5t2K0tbdMHioFMsp6YTJlUFTS5C+\ntvYHA0pOSjt4Zr/J1B+Z7Mpv079tUsG9qAnOhQpEP8TG/qfa0fsh1otKWmiE\nO9B1IoOyTxXfzIZCf5pBjrIGFLH6f+tGy84AG3YtzsgQuppN5q7k9kC9RCLf\n4SRUYSKsWvG6TuLMRJ/OqPrDChH91gBDCvVJvHFTC0J1K9AK5BIS3WucfTux\nA9t2gk1LkQjoicCP4w5TSWNtzEWmbrgBNQxR+VrwAPjRrLXlhMRul1xdT1s0\nEUBlDT/U85BrXD5/QOkmRwiedCCq2/o1/9+OKrmXAf88uOPK9nQeWJ4HmBwh\nt3pffFrBJp0t8q4J/hoO5AvXSHDyUNY9jxe4cyQ3CNJ3IQc9TkJoYnadownh\nntkLtQqskeOHazBpJaZyLWchUKdvRWrxFTHhLBG9u/3I5gnyAVVuSqHlQ3sl\nGlQB\r\n=vfWr\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGb6OYJes1FtYPD4rib0y7c25MJZW0gIS2Z3u70ocQR7AiEA3HQWPdd7koJyK73wcKkNf0iZ3L29XitXyiKD0/PTGCA="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.8_1622594501062_0.309077315036588"},"_hasShrinkwrap":false},"0.0.9":{"name":"stagnant","version":"0.0.9","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"4ffe851556b4cd4664d6f931fd083bebfb8a9972","_id":"stagnant@0.0.9","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-iIQgq0sFnYGdpumVzI5i+ZczwDJ0fDT2kj6QYDmpIgGpsikGefC04O1fkvCtq73UKi/IJYnfI4kAZqpOErhM2w==","shasum":"25a2811408054a01e8f8fe97e510a36e78a70be6","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.9.tgz","fileCount":16,"unpackedSize":179929,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttWzCRA9TVsSAnZWagAATOEQAIvad9J9hgMBMCAJ364G\nxbJG00rt6pw07hM5WebNpHfo8jsrrLE3Mz1dZq5UMuyl4oSXIg/e4c8hHue7\nK5L15sB0xxxUw+TCEMRuSCe3Yp4hhOjRKzKCBoVO2LzMblsyP5S7AID+t2fI\nodSgbo+OMFvJhcU7Jx5mi+BMCnMJjLXVfNcoNn4SsICFFYTFPGT07LhKD0a2\nEeKoUcb9zCt6/U4jLHLPts1qIjCqiK9vrTTbCNUYrPWX4Jjq3UdzRAHYqc8n\nw72DAXHfTjMPPHF3Qs7Wcb/uM8+B+G4g2zkutZmyJzC/TxTIjwuxo/whtESD\nNLV8kolh743neaA7jJlevZKy4AhmIncWWtTxyrmc4TAncs/qlXVH/vH8hYqd\nqMF426I8qCTpEU19q2aUyvXYufRQUSfDWcUby53T3do4a/C8voaEIO3N7uUg\n4CQW2ktg6YXqPv14azRXPZ/e1T+Lmi0gNmZUzq3cdWi20aJ9M0jayX3DrJp+\n01SkfIrrEjDcNgO4yR1+//E/FFJVFGkMtmsn9wzhDzLEC9wB3YaVZgdjUWOe\ngCI9+Jele1AIzseNt2DVLxpH3N6YNSLUbxlV5jYS9YvuJANMte2tPA29L0ts\naJJSYsZ9+3j/p7PCMDeX6tmLKMNOjm+14ellE0G02Nc+TvdEOrgg207YzM7B\neVYb\r\n=o21R\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDFsi4Zb/vBXu3rxWTFz97dZ8PgyZO1h87/143nGZMMsAIhAKdXKBUk6jQFYkK+m0Y+wnDdBCq8UBfWkTk1ZfnpwWib"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.9_1622594994652_0.4984350080294657"},"_hasShrinkwrap":false},"0.0.10":{"name":"stagnant","version":"0.0.10","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"483c1f454ceab8c1791389d3f2565c317931e17a","_id":"stagnant@0.0.10","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-xkdJwtZ5rLC5ksaYdCFubHStXuWP6l3KklJ2enBQkc5KlfErsvHCjgObCqMpQeV8eOqQTFffij8OrekOYClfIg==","shasum":"6f361986cd449be70651a29be0fda40b58eb32d9","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.10.tgz","fileCount":16,"unpackedSize":179930,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttXuCRA9TVsSAnZWagAADHUP/j/Mz7D+35/fGb6J2awB\nsiMh/NbkZqqq3DPD+yssWHjw/TGN+rhxVTa70iHI9cpd+lHllphB8PliakCC\npOijzxn9VJArZr3AhvMoDB/y/F5JuA0u7BhqcS/P9FiMNDroW5NTaU9AzzOL\nO5LU7SPg/408wQyVfCDg02nzz+i3iK0/WtXYfbGd/zF+ibFdfA2VjiEmdrOh\nJYbieF1PI+bm6lgMqfdJaZfd+hBXeSwbrQo8OdfA/1ENrApi24SgB441jS29\nh2kgFsxYtH0+Pkt05zq417f6+yh6T8ei1zj3WmiA+JgujJrzgfC4WtqhYrFM\nad0rHtLBjY4egt7+5e554CpfLusbvKkmtlvcqtxPE0n9z7bZxd3zjHaimOXU\nyrHCRiPhOQe5k3VMnVreI2EFIvy5BVMIhGaatalK3+xzJIrbveWyGOXjaCWR\nRA53OS+iY+pAe2vGEvYVpJeMMnlut49A4MSwaCEEQwmbCSWx6hMg0W2g3frM\ng7+6VAOAMF1/fz8kiM+oMkRbzGFhNPsMF9IhYOM270WPy7mwWAgvVWw2Y9oH\nNK8Nc79IQ3oCnCsux9wmvOHhlCa4PRap/6nVszvRoHJlmbZevVuHWs9G7ybP\nVkcJ6G7+i2xd2fa2j/cnlZJ38RigTakR33QTUApypIegzH69yjqkAUU1oHXc\nsAyj\r\n=N30D\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIA97YHqWCxF93wuv5Vwb8/hY8RCBlRby5vvqlviGIb4DAiEA9/SkXmc1vCA9WIcsP27OoAgX9FD00+BanIHwL+cPXnM="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.10_1622595053538_0.9193374391653086"},"_hasShrinkwrap":false},"0.0.11":{"name":"stagnant","version":"0.0.11","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"abb6caadcf51e085bfba58ae8f550e7aff001505","_id":"stagnant@0.0.11","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-NYTRmHjwViaWikqP5RvO/FVmkdW0SJ9B1WzmOyQ+DbpDz8HxQtcJhc5NlmYa7/c4/GkSIYs/pVxZtG4I0TUKMg==","shasum":"9ce805287ec0af819170e7b6b81609476908a9a1","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.11.tgz","fileCount":14,"unpackedSize":127469,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtta3CRA9TVsSAnZWagAAr8YP/j5P7uRtycaVKtK1WIcZ\nbNut7XbDC4n/s/W9+dMe10QHeg9K6yG8ipKraBSFQVMmM/WP+1ALMBt3hDVV\nl9TM123ge/LcFtAW8lumVBakwfO7qHi1bh4fJwXAzDSL1tpB944w4Ge7UwIM\n9yZPTBiQWHRlGTE7Nok3PaHhD1iecJzY7mVIjb273ZNagiwuFUi29ulwsJm8\neULRattpOXsf7UlZTmRR6CRWhc8xLYPF2SHcPmZTt+Y0Hop1ZOVD53Jj0L3h\n983GH+yHtBJBFpD54aBhJQgonBr8qhSL2gt7teWDO/ZmVdc9NO+i/DBZZDXx\n7C5zajA8JyUxjIhwZ064C7plf0yo5XVnhh3gjKhapWsJ8WyLv0aADzx6hJOW\nuTxiGymRtkGQVAIqK6bE+VA/iqZQjVLisNC/SuhfAWSm4EmlP1WpeJ39+dGv\nYOepagCsUq5RuddEcrjj++ZHd+R4BbnY+NCaC7g8xUNm0pOpmITu3O24S9Wh\nk81CHb0uqw3lpgS2OczXqdCmEvM9YTgLUgB7iYK5XLty0l7OOmM3fJqdezpJ\nM1ady5GVzi94SrF+phWZjzkUUFXBNh+tk+H+PpdIVPbnJsY1wgxvUdwIYOA4\n9cCx2xTE50sOCu/i2dfqDIo+WwfF2otUenLRkSTl+AqzZoKQbSHFIs+HguyT\naEAe\r\n=dAPL\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCy54kvFp8BCSxU9ECBhWunv+QIu7Qm6f583sbqT4KHGgIgV0ymZeJk4sPkBn2IodsNDgwC0aYW9e0JWjZj/iCtmpA="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.11_1622595254830_0.07912172225163716"},"_hasShrinkwrap":false},"0.0.12":{"name":"stagnant","version":"0.0.12","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"c5d01015f28b040ae64be151df2d9f1e0b637fa6","_id":"stagnant@0.0.12","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-ogo1jWvQYvWK6eeU7xDD7MveCG2un0Y5WemrAwyAzaZzRtjdyhqFryDmSMVzww+jY0YXSq96U3fRuS21f1183Q==","shasum":"2d38c9bef9cbca15fb874900c8193d2cc3891720","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.12.tgz","fileCount":14,"unpackedSize":127514,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttdaCRA9TVsSAnZWagAAsrIP/ipEGadsKoXpq4OTS2BE\nzn9+i1nN/PGdk2c1XDuGoLtz0l98yoCpwPz4vBsZVwc5HQkaD+J76UMCNd4K\nf3LNmdklC1OR0r8w2GtZ5QVIFoujMwQ5iSKkcvs/kN+GZ+ap+cWFdl9Y3T/n\nhEMPq/yK5oQdUxcwfG00PrZ5zk5GsvV6hup74ke0G3WlgSGDvb0goXsY9n+j\nZHpJg0J2X0BM8R7PVZAmVW+KxlcqHIYC8xUhzgisjbMLJEC55VSYjzgyr3xX\nHOaWfIkogMGaHGcWzvYGy0NvLbQ+f7x0/q07/M+J2/DJmN9qWswa993Bidhb\nMkVRoAi+5yp8IEk8RJCQrnUc15UQqpub5n0l9kiSjDjciaXADzpyVDbIx4e8\n0r4Pb/EaFfUMJQ6mOgTiA1/m3J3nbxBZNqvD+IKvGp99DCjoov4/XH9IaG8V\nelgZcw2Yz2fqeK5t6TbjruHEZfH5376StMgXOY58ams9FxpAvCPZCefR5YUi\nNuBEG3njy2+D3cZ7M8g8YYRZv/e1ozCwgqktHbxUUAJLHrdCx4VoqQJuBdmN\n0UAmCTp8f3qUDhwWfo/rlXOFDWNumo4EOQGEbgCkU1XNPnkiZNRcV8wdNSnm\nDA+GxlaILDFbDj6Mj8r0tv9QMAsSpcKbtlmrGFYY/AXnR7eypyTwkRjPiuJs\nAWvH\r\n=cL5W\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGkhOOFViwWJraKjNF1XCDRuOtF57WbaFsItuAxG4PIHAiBtUvtVrKkQx07XMoP1OH/6Xol0yWgve69S5VIgvjWomQ=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.12_1622595417635_0.6433805345425407"},"_hasShrinkwrap":false},"0.0.13":{"name":"stagnant","version":"0.0.13","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"81d83c357886cd0331bce4c0f505f100149c5775","_id":"stagnant@0.0.13","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-S4IiC7fJ11cpwham3hmIaEahUkFx4Mg6Nm3HD73K7w4v2ETxb6/jNMf+zPBrLfImPW6WwYOmGu7w/f5a82XiZw==","shasum":"8fc8e0eef42b9611eeae5a14db5771247a7cac85","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.13.tgz","fileCount":14,"unpackedSize":127509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttfLCRA9TVsSAnZWagAAqE8P/19OeIZW+DQaMo046yWO\n3MN5X7dk9hYuMwoWsqMoyV5XX+QwIqqk09r7+t+Dl/UryfBJ4Oavs65UUTXS\nmXSysBbhSSx1DMrruM2woVBdRsvdnV0pQWInFveYnI48XOLULUmFzqvsw89t\nELHVxAbrFYZoiEz8bO2qhQFahuFivFOMAM3qrcpfVabLP7qcMzKT5v9CLYnE\n3fW1BpJRi4OB8EWc0rQWT1uKgZwUwu2TRxkWVnitYhL+kZ1h2i6OmD57vbxY\nS/jmMCVhVNVHaVBACN1iQ+THbZ/LNDO0eBfZ9d71CZVxYxiMOsOeNhCQJkWr\ntXehvkRzYDhJXPAh0V4CxytrbSDE5ZkcvD+zl2snwlFCvaHA5O5L62gbSYaJ\nb44/9Fha03MnGMOv+UKrItKUr3gvVP3dgR/PAo/O2CMfwispVDxVmNwp3lPn\nyRDJUN9Sxo0RIA49gzf9xh1tpS9q4ZURua9rRDJ6UQc6BGABaUT9UR0kH6x6\nKZjQfiV0uRHOu13qmd9wrUBtRn/lKxBM5jzfhvmz8j8D+svOQWIo4i3ZBbnW\n3s2xW9IFWKGdxZ40xIJMiUUr2o5zaXlZLMetzvLiIlPV2CJwJXcQRVwbtf/Y\nDeapP3Gn+gqiD0xxw/VUB8aZzSf8bXzXpvFnYo2b3kVM2s9QmDvWxPztGk7c\nf50V\r\n=B1E1\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCwhWHDnziZrydKznU3z8V0hm8L87gmYYRuLJ4PI6M/9QIgNcWGEk76QNBJ6vTF1/mr/VSh5V6cwAYaH6Bl083J3/0="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.13_1622595530900_0.6790354002586447"},"_hasShrinkwrap":false},"0.0.14":{"name":"stagnant","version":"0.0.14","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"a50f4ba3dcd32fbd93402bffe740c0902e9339dd","_id":"stagnant@0.0.14","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-gu2hgiRm4QaHDLQ20FXNPm0yckZrxW5RyDnOkqa7a0vxlbGZ+3NlahrUq+gUoOx/NiCAeas+pjm5dOD3i9B+dg==","shasum":"35d4c25e6e0be91463a17ea8b6902291ade3154d","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.14.tgz","fileCount":14,"unpackedSize":127513,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttgPCRA9TVsSAnZWagAAVGgP/RQDHIX9z4+0CuLBCYdO\nZ+Mpm+87sbks9UHHgrpHTZMDpPHCAIG+pvJyv1Ccv+9aNAjwBazGXR6YhE8e\nTmxKRMGl1Nfr0ZON4z7KYyUeK1tO5TGa/aFNlJyr2D4gNdigl9MnDiM/wpNy\nf6hGKpTdsfiMQ4efAgSinA4vVkk9M0Aqu/LsZmyS9q+OpYSCERNSy4uBqKig\nHH2KzrYnwIPaa4diAB2XLeAMcGdevHZTN5GKfubKozpsHUHoSb+560A6Nd6W\nqVDwXGRXneCYGcrX0aex0DFpcM8tFJB1DU0Lp5WzUiAmj6YFdltqiYcD7WgR\nE82/6p8cYSetzZpPvDr5VCa86GJKEjGcoTBkf/JYK3XVm+cnwnac08xLjIyJ\nC5XcEWRCEKiMhCTYsKla8ZurrGKl4fgh9l79/Zp07WlJlhD36NBUt2q2JGhq\ni2e9sdP7JhiLezOlyoPSKxh/NJwnm0c4Ft/yPJx4K2JYcRWuXGR/51Vr8jWd\nuvWOph7TC9E3ePQqRmf+vWJTx3/hjQKVmiT4n3pIhLkhu/67iFOQt1Jx8jrB\njE26Rxlx0u3wbPsYjHyPQYeIUYj3xnTFXR9ccJHxi8fnXzredURcBqXGIzmm\nH0mHxZzMjB7Ua5tdW+18oDFW6HKfg+1RYE4q8/afnsxyemPPlZ2O5CSmTCaV\noj3+\r\n=D7KN\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDjtUBPyr9QrriG0dXsRMXR+lQhAxY7TpB4ue/AyNHp1QIhAIT/ma0nfl8i90yUgepTROBc91hCWA2pNecdT9ziGEaD"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.14_1622595598656_0.1372587032055792"},"_hasShrinkwrap":false},"0.0.15":{"name":"stagnant","version":"0.0.15","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"4e2d457275027052749d597c9b403f07afb1e6d2","_id":"stagnant@0.0.15","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-faolKekz6WRpmTFwXajHgLFQdiZBnWunT+eudUrHmhcQdwuBmQp2gWxU+dOtqg1kHAbM7Kz9qTSlOvU1XN3dDQ==","shasum":"423543c0fd6d83bd6aef12e50987e04dd3824a9c","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.15.tgz","fileCount":14,"unpackedSize":127486,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtthOCRA9TVsSAnZWagAAk4kP/0DEbBhD/8HaFV15SVGM\nZa18MAAImsWnl8svhQVRpVIjXCovie0aE8iP/6kOsVqSniiWLmT+OPijaL4V\njs9SWNPRbk4/2S0qs078f18TkRsMDvnOmfiJWnpUDhyxAA20M0IKk10omMZH\nmJ94lwlb02HWDif3s5qewVXRLjU5T6Hb5XtOfhyh0Of0FKbTgiJy6LTjtEBX\nA8KNCAitmvGbGJsN0/YXRZQ78aVZfI7fZOegT7uLfR/qTYmUgbZJ90qUOjSo\nLUR+b1co3E+pZ06f89v/q+LTMvn82kLcmWXkNX/MwK2Ifl8tlDFi25xvIiuw\n5dwHwd5Knx33Qb2kyS/I500768AEtqLTcwSJHV9dPgGUpF0ipa9qi6LwZxbk\nJIdZ2CxuXyIPTwWRsfpPvcuLLP0QwDaLMpMVdI+4hp7fv+ZK/jS88bVdJuhS\nUYaLFns+YpZJeG4mAN28reY+qdFr12HD+5Ig1DcCoSwL+tgdmgzNHV36YAts\n3jIRSQMLSUqLxBuAr5x4CRwwO4wze+HwMIPmTJ+V/NZSJeQK5d93r2GMJ9uR\ngiwqb446opnfPpRTSrE0Xxm3jdrQwhvdzFLLBntHiY7eLZN0r4kgfrUOhewr\nu4St9ZQsCVpeP8rOkV7iEQCgV3DKUIJ2u3cTTfMptwXQWWJoE4EMmTxmusUk\nxh3Z\r\n=E/a/\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDmtkL8kvRBmBdq9UAU4W7a7aS7k4yyaoo94jFGt+CkvAIgEMyVxUrHbksACNZyml0WpAdjrgTzKQXqSSxqlJSk++k="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.15_1622595662114_0.19205973399381815"},"_hasShrinkwrap":false},"0.0.16":{"name":"stagnant","version":"0.0.16","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"08e69189e2bc44211397e4e8f440d134c14f898e","_id":"stagnant@0.0.16","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-TU4Q+8DqGud7Xu2G3yopDZsI6TlhqkbpVTZ7h1+ht57bEdmB8SU6VjsoKBsyRd9vdLppeKVwwiw1pxyYHuYhRg==","shasum":"b8cbadfa9955711d92ec3c958e43744f98959ac7","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.16.tgz","fileCount":14,"unpackedSize":127457,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttivCRA9TVsSAnZWagAAwyIP/jejM0BRpGjH1hP+x65n\nT02r2VasgxczRUrgHmTE+CLG8+kRhiPG04+MzSYGEZHeNzAJpo8w5Oxba/Ic\ngLGRIe/2JdoWFaMYjfdkg2XkAX4hZH7EZf4p+NNGuc7m+EajfY7u/7/X06cH\nhrbs/9b+PSIbCdCP8G/TGJQG5eoznpjFc2eQkPiyYVWgznr0JZqxX/tjFJkQ\n/Y8i+tdSRFJIt84N6PlVEJdy9EN8a7Ibo1dKzMuDPNCNRxJsH8p62i6/DL80\nmnLkIwTpFYdCMuTRysueLZYiiPP7fzZsQVsYRQjWdTEU/IfRJ4mwnQMaDl4g\ntet3cM2sZJtraHgOwu29oG8+kflzaGFEs+IhE1e30VmLo/SbVU+E6rRcpSDD\ncY1zHirJ1hyy2EPmjRn4xgs3CRCXveGjygCuJHhQjqHK2oxTDHdibqjA8Ib/\nlxPFJX3aoU/l/4Xamm+lLiY/fbg0hwbLtJtUOF0RUfMobSuTowOGO7cUytm7\nTe0+CyM7CgwSVZzwPuzhDyButpfcnITVy7vU1cJSIVCegtLPzXAk7CaW+aPo\nrl0EWHKn5hsysnsVTMUkbt5WiYW1W6vixaCHL8TB1ECwlGWtwzePWSV5cqZq\nx2+rYcW2CdCo9uisKXfsHhUywEP90lPDQAyWXpset1xWpXh3Fv+zfP4cTnnH\n3RQj\r\n=osZk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIA8EJc6c1fsEfY7yaJ9wy4l36hJkVSB30atPS5wQo6NQAiEA/BRi7lIFbTjz/7361yTmJ3Pdn8MSy8+rHkQBDQs2U6Y="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.16_1622595759253_0.9550701842454412"},"_hasShrinkwrap":false},"0.0.17":{"name":"stagnant","version":"0.0.17","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"68e261103932762c1a703d9074d00866cbdf6e48","_id":"stagnant@0.0.17","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-6M8yk2clVCKIFzoQZ+4IcaMi1a2aSckslv1mhyfQc+ZJCLlFCuowdwVsP2uJCsZjZlNjAAwbA7e7WXh0A4f6DQ==","shasum":"1671efbf0397c8d6fde70fda0a96085cecd36574","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.17.tgz","fileCount":14,"unpackedSize":126901,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgttkeCRA9TVsSAnZWagAA3KcP/148aXLIV1S5zpc7xujq\nY0Pik1/KlxuR7Tl9rlcSc0EAW0KbOEO++n9A/venL1WWzFc96UVuWXDsxcu4\nky3IFb43KIC/Bi4kOlAZEmTVko9++42DBioJ4cdBD7QrZHpYFqveiJURPS/a\nmfdzTTV/spA2Bc9rkVZ0SLvbuQz3JUeUZhoQR1wTPwe0SY7cD2ZzlSYZkKP5\nAznZ+wjwGxjoOJtkQJPLee8VqxVvSWxSQRWvAj92CBmBH750c8Ktg5u0K0Th\nv8mTqal+ghh2PchORAHtmPWaWbfKJTUyvNl2t56/6qmpf7sw+kV4eVCpGzvp\nLaRGxXWepWV8zxBrd6fn/j/34+qQPKQlvepPlH6U9AqfrcRWks0jHxk2cEBy\nETbbB0UKtqotqpUBQQWAb7pbCPuJ7yZH45e5Rf4iFnYhvEdLk8kZZY83W9wf\nkIrVugJsvy0EgPVsS0bdWw4gbjTFjAaLX5tDvZ6mJiOAX8FCHm7YJzVlTfMm\nTCfRu9jO0U4XiodtUtBPjKZMxROYRY9rmt7pjLNioc5Y03w9nAUedNaoXd99\nKcA5JdsiLSt4iReTx3HupomEMgEHy+iOHNCruaZLnG8ClL5s0E8F0/mDZbVB\nWsjILQl7K71uAWu433CxQQWdUWUdUUYEw9Yl7SEkfRjfivF+h0yxLqSwpbLX\nVw7G\r\n=Ohoy\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBFh3z4f4TtJ4im9f0xTBXssgVCLrFAfWVzKhwucqGkrAiEAkBDkkUgi9XvvOobwLzbwlJ/CnLrEiM3AGUBp1K1ak3o="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.17_1622595869834_0.7254908311958181"},"_hasShrinkwrap":false},"0.0.18":{"name":"stagnant","version":"0.0.18","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"6b0f2e4e1823f17f5d46dedb29fab92b92aff6fb","_id":"stagnant@0.0.18","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-yVAKfebs88jALpWHJH7IQXYwxXhCgbNYTV7OoetCHQxPAbz7+CVNUeyTpIg6jbISzFRUfj6heSLPILwBTfTOYw==","shasum":"945c7ea27c0d619094184d4ef7d02b4a986367c5","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.18.tgz","fileCount":14,"unpackedSize":132132,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtuN9CRA9TVsSAnZWagAA8J0P/RrqeSZ7J6Hf9MKNqxaJ\n5qM6Du/SkfkiBfDWaXMq84k1Et1d59/LRpoDt48+jnH1L7Ngztu74igHUa4G\nZbixhSJMdy5zczU2J3O55BDvhrcHlMlQ3/aKQhFFxZxSx24nL5FYrrNg6plw\nljWzv8mfYxw6bUJ04aWypJWQjoos4nZdufNGYCBW941tOW4viay3ZJj0zgeM\nJLYyISYsmld88NWdGkbELFij+ZIovkBe6oGP3bLvvWEmS2quFOJRkC/gB8fo\nAVyZsNJWKH1xZF6YBc7AuAuYOFZY89rDP3nJUhLLNMpqqJnsDKPBt1aPzyUI\nfkGNPsiUFQa0uqkFXZ1mlfvK8RuQHQFG3luq5QNSwbbXWkwheQvXDf/fs5YM\nmXalDTd9fDtTMPci25td/CwcsWSDqNNID1t8Fk0twMzvMGZizXMJudnVubOL\nl7CQtVRoHb3Uwn8Ylggb2235x8CCCyvdKrWwx6BLUmLipBX9VQdH6Sbe5V87\nflpEVbB2Bt+eBAekG47QhovdWc7CgMbDRoq2r06URjpd0afPrYlzA4cUkCVY\nsdp6qEHGxGOnzG2WrRY01U/VCm8JGE5yYI6Km6YfNd2rELN+QdUYeZ8Gn496\nCj5YxbKu4SrxVX3Rai+oxebgVZ6vxL1NzKlZB24lyJ3S9xqFpa8qH/+j/2PU\n9uUq\r\n=XVuk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEut+fr4Kb5BV5SCPuDcmGHIX5yFxlho9U9zd1MeapE0AiAH9+fq+W2pR944OFG4iGGlWwDdcwGrhuHDiAno2giC8w=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.18_1622598525309_0.9302766713593504"},"_hasShrinkwrap":false},"0.0.19":{"name":"stagnant","version":"0.0.19","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.js","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"caf85943563359ed943620d88f9d6b3898c38abf","_id":"stagnant@0.0.19","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-X49igZyUoph92SR/UXrQIbbNXaUpGOdzeZhCAZfRI0SDnU6qvcmWOztBEHvjqHN2xX3bZip1SORLYXzoZ2Y5JA==","shasum":"500e2f4793e12f1d52ca8a0c20694e22aec3ce6f","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.19.tgz","fileCount":14,"unpackedSize":134424,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtuQvCRA9TVsSAnZWagAAWf8P/3Rck3XEWbBZ/bncFARM\nCdqprNr60+sUqTUMakNRSonWm5owZJl2fULO5JzSjAfgKyc+BmDPldrejt72\nzDZUMfgOOkVAlWNY2W9CA0APoLMIR8bKbbTa4+ew3UCWgEHWQLL/w70NkT35\nyt8UfriMkSBaEUANLxdZLgF2JHqsaEjD0QQxmvuY6gxmoj0aFjc1ga6rBml+\nPaaLTtdlaHtky0ZWPpO3V36fH1fbsD7iDqNaWpbxP6sbULpvTy4sHhpfJd58\n/qXRNPTrT2ZsHpW5WtYK4A+BReJCm9cAtaMSWHgC5dqv/O8HGkkUD3HNjWRZ\nsWD4X9qEj7L4WbChSROrATxe62AJknVem5QH+otYIeyYdNahVEhZ/Xjqwux4\nyH0lD86tK3PL0V0+vmlMrmBzobS/FWomlg8nQEpKw6XFQWKUX88XBuSLRtNy\nXQ2VYOQGn1E+2tG/QLWei+q/wZ7GK7IA4BtMwqlI/ipI/ZrkkqpSPJLef/GN\nm4IBfFJLDiaYie8oOZskbQrti1U//fazr3lqAYnv0liD6y1jB1ghXJfVhzlc\nKaPw6Au0x/HY8gcQNdxx3tp78Izi6zUe+4Nmj+N3IUkZijTWSSd5pauOrmEX\nbES8vIpmZm3tQ0W6tY4nfHVSxTclZ/ZQLex/JbCgU06fqAIdGKkZihK9B2Il\nkpOQ\r\n=SDcd\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBo5SL7AtHsmjrx95n9d4LExbzGeBQcp9b/YNMEKTjIFAiA0TVpfptNTloJq2SqBPk5cB8opgRqtLawZvqVu4IfqkQ=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.19_1622598702813_0.5486524068750303"},"_hasShrinkwrap":false},"0.0.21":{"name":"stagnant","version":"0.0.21","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"84443f4a0d8516f9227eb858f6cb92e7c719ba44","_id":"stagnant@0.0.21","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-7FoMfUzA2NSA7vTociVZQtdKWbPXVbRsyRgsWyJNXafyOUhko6B3skEk/ywsU6eq77FiR9VGISJ1Johf8gbcng==","shasum":"fd9e0f4ed3d43286c71be782854b0fca1088151d","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.21.tgz","fileCount":12,"unpackedSize":146124,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgue8ZCRA9TVsSAnZWagAAgvQP/RuwCdPS+VFNQ2Y49rHh\nJQueaLLadPHNEIAu4WeuadcgCeb9wdXsMRHgpIbggj+pQTpooXWff7czGq5o\nDQsICNJHvGMYr/I8TGhI4kYTJOZrAV2g3s4Z9i9kytY/rCWMhGcU3w66gB9b\nxKSsTMBj/41V4q9jxdUZVtuhw+E0XI8hTX2R6lFuUWlPkizbf+/EeOdq4vbb\nBAmMN9q4yG5lsjBdKc5MQUvowMydLUCIG/SQoquQvGhk/watoJD5qgL3QdCk\n/xb52GofqCh92Ecn+kQEZuirWpih8/+RMbc+Ghpz1XpxqMGC9aGmjZnrEITO\nneAeC+OlyooMH3ZhNYMfi0FM0eOVBAv7GMipMOmqlCS0nuo2BEsNsx1l4as3\nfoCgsnI29g5u9pldg7jNWO2+BY0g9Ixz1A08G97y7mLeGbzLxZJhHf/WzTBn\nrsJT0r9o8RnCqBT5/CzTAhRaiR1v3ImLqHWlNYCi78VcKVSsUwSNhppk/mDP\nUILuePFR7ydVSb3MLtlBhlKzayPK1e/lK/Ackxp/NrjDnDeqor3F5XkJ1M55\n7Vm7mXjiqHhl60QZyu09Uuihd4myYvIoFkxHnkMYWhSGeUjwgl+Ilbf9B0vx\nXsGDKDcbNLBRB2HiMmP9n7fo4Q1DLm2ikb2l/S8MgeLtFybNctfDWSpMOutO\naX0i\r\n=OLlK\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEsuxwPeePRHKRdSqWfacn2s7XQ3oQwWMG6rdddS3SLqAiEA6UKwra1g5/wkNQtPMJEOrI2Xs0+2Em0w994q40Mq+Xk="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.21_1622798105524_0.42716060453548166"},"_hasShrinkwrap":false},"0.0.22":{"name":"stagnant","version":"0.0.22","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"2740b986eb291444b843c1e818f78f5c5dcf019f","_id":"stagnant@0.0.22","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-Hl0DID6UbPBzPEDeeq0PVcU0av7S06SBS/fmf47hr8T6bzEUwCZnH1m7r+Y2rqzJgxuwXcmmG7ppacWhXD8e/g==","shasum":"bcbd0502e474eac35c58d0e543dc6c31eaf30921","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.22.tgz","fileCount":12,"unpackedSize":143303,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguxo0CRA9TVsSAnZWagAA+pwQAIOv7VoOteFYSlJ4jVqg\ntv3hbRATEjlD+OS///mSsQJ6Xv2VI50+lgE5UHIuF7wUdNkzWTL8Sdb53iXc\nH4aHsdBMRkOhLZBuB3pqK44gRld9m6mlqU2PNa+zD8QpfVAtgqPv5FoWfD9n\nZdIbKUIpS8WrQPV7okPqYntlK1Dq8TjIZFIBJRHwNUVUY8Uy1cPialCHtkkD\nrzV3jL3BRUxMUoBJTEoZe9UUXUrLSc/UbJPX3RwkD+359q1yHlCkT0Gln9Bx\nGhSS4zSwIiGsONP73wsUcPsZJU00OSb0JUByRlxpTZyP7qIbm618BRruPlmb\nCCJUKDIej0p+DTA/YENxQms/mvcVEkaPlqNrATfnkOKHFqR3XrGXK1dPM408\nwio0Uuj+C0qvvHQPRPXcKFzLpNGZM5YqQyFII9+xb7wqiF1CsdeIsiq6/Ba1\n2qZRcsQD5SoZlRYD/1EUjqyXbHadiNj0ltRWXqhuTNi0GN4slwZc6bnyFKbS\nKUKUYFgOg6YXk28EP0Ic++zETw+nJSCkoNASu6Zr3SW3qBZTo+qERagk9aS7\nExidt/uJjDGegUyKYBXH/oZv5qymcXbPspIv9UxCQz/IblU4dd9NTcgSDvBj\nPJcmlnLH+kMLSPhmwZ8FF/LST/4MMUBh6gRIknIatFFntzjfRwd0Nd0J3mzz\nrMMZ\r\n=xhKc\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCKjDSEHYelM9FM11sF4SnA94CKXTk9Zl9wN8dhoILwVgIhALBpoAlMIHqFnl2PbzNTzTkdgfbPXZcVqwdve0f8WOiz"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.22_1622874676057_0.287758746847093"},"_hasShrinkwrap":false},"0.0.23":{"name":"stagnant","version":"0.0.23","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"cd9e0c830cc5cd1e88cc4ad3fe301f269cb5bcc8","_id":"stagnant@0.0.23","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-mdUiX1Lv9+8Auly04/JGympGGU/j7Ksi9aTuBkNXdAIPTd0ZaNtjLMfjwFE1LNMZ3fkV2ZICnkEKHi98Iy3FqA==","shasum":"a3d7dd33faaf1df247f8883edb3b7015e5f42712","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.23.tgz","fileCount":12,"unpackedSize":143303,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguxybCRA9TVsSAnZWagAAJ4AP/2wnt4FbulBRJpwOjHoB\nnPRAig84IFRiWAJuqyRdiseKSdqKsP0NwvSHvDNSVD1sjmo+Hn3eW4IluqSp\n8Xnkprd+dy2lywRL4+Ok/oi49zY/ymaUmISnbJZDdUbA7tsKyfrOoy1XLmFv\nfRWN4xmZZAvIw2KbLoO+5OYrTJdjzV7Nn9z3Q3sxLpRH0a+Gyjp4DRZUvrjC\nkf3IHBg40O96qcxQ8r0igZrYt5YO6oQVgkJa0Tn8y5hCZfg6cNP24fVxXe6q\nOD4LItGWLwjgnOCQhoIAttAZLd86M3FkNcrinZpaZMupHGOlUFlKJkoW9LGI\n1hEux2hahXG8pkCk6tIkeWNBe6oVmBbbZiIq8ejO9mGch2az7SD/pmlpoQHk\nfXZM7LrWYPba9Sdzaihh3f8XKwt9oPjn1S4uWEUVjmp7aWpPChpURLo6tFBk\np1XB0IpZUNZqPe3A8S7Vzl2fTNTfuaSljgOkzKEFibRWXX+mUTAB5efTEo2I\nRiViUB3D9cwbhFWc8fXf0gVkcwiH+AryHuI0cF3hHUlmyMLgr3ZXkn53H2Fk\nldNrk1yODyQYSMoeqlewrOfJk798b0DWQyBgpBIe30bhvsBncouj69mFoF1H\nKi59C8TZZZi+LV6mseI/MrHdmSD7JPBsHxrTt2Kc9J6QBX13YnmWLjqQXhGG\nfYCm\r\n=azcL\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAVXzsxbwnr9c0gPtBrP+8WY1XIsqPYcT1ukURNc3QGbAiEAsqZWjChpbZZ/zduaYTuMXeu9ocBryyYlD6pzmNZ7UQw="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.23_1622875291410_0.9965055402063243"},"_hasShrinkwrap":false},"0.0.24":{"name":"stagnant","version":"0.0.24","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"11f31f8ab3364bf37365e513509913d9d49f9917","_id":"stagnant@0.0.24","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-ETmv0Xjq+FMJjIw7tEL6VRmN9FayKRm49jnnGawX6mlDXgdkEZdGSKXNA4xaTqPtaFfLVuPR9SKLXNOKfXTiXQ==","shasum":"0a945b5ccb4da39185bc61e38664cac9232b6af6","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.24.tgz","fileCount":12,"unpackedSize":144357,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguyIPCRA9TVsSAnZWagAAnGQP+wQHme8yAyfHAt5lK0Q9\nzFi8zp0RzghkN18LKPHGBpEgFzhRqXA7NoumMvDkvlOp2vsC6aiMq9pEEJfL\nzibbgGLQ3lbdJ4fJ4bRZ4aTAzUQD+YecasRhY5yepQ6uPU7so8GdzbGNppw/\nMAb1ueRuGvjSjf4aBezKQjxcMhGPCcxCyFI+ICKxdzmZSHSG6PLjxh3QYvk2\nWRWJ3ZY+yVqq6jVa3Bt7PltvY+PO/iHsrcXI5/se4vP6HRRbfnFSgidWfq2o\nbjglj5DFr40ZKjxUhjw1zSt7wIbXPiTm4kiOjx/W89ljJIIyZpwK1+pBGWJ6\nMhQp1Ho0YOJF0KymoaoFh+MK0zEnJteR3oZbSf0sqougEwUyvaXB89jZ4dLw\n1ur+mq1ZWF8W1wVppits75dbZXZNcJaM6NjIXtuwE/SzNcKpk4Iyc3smPKQS\nWDLdaCRol37oUlIajlJT2d4jv5JUNYUDnfdyS8mHvNvCv2hBXXK4Yrc3iGaJ\ng/xaplKPfa8RzNztxtctr0a1SD2GR8yEmyUxBVjmldPgtKFoNjU5itIyZe4M\nZySL3elVjZJHPoxu4wHnTvG2towQy+dX8oHfwUbtxfStfxkmyVmq+sL1Fpbo\nqo5j1sU++Xuf20ZUCaNuEuwR34Z83WL/X8hpJLwRHWpoqBh6GE94zOEFY7Ue\n2mxB\r\n=CsMY\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDaL0dUZcWlXcl+9cqjbKB8bXGe/tijOpEKo9KhxbZYzAIgbaZAh1y0wXERY18mh6XSenc+n+qnvCjDVk4+cM3sbm8="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.24_1622876686776_0.2903651190985852"},"_hasShrinkwrap":false},"0.0.25":{"name":"stagnant","version":"0.0.25","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"53f23bb9ef7ccfbe8117d405a924023b661d41b7","_id":"stagnant@0.0.25","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-eNJDMhYEwnHLPfN9dZ2iJONXPLkoHbjNK1NlbsakY5FOnAbPeJRZsBzH55Ci5YznmdBQrBikC7zsA4+f0C0Dlg==","shasum":"9e6ae6314d0b3061efe089d52d7d3463bf7b25a3","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.25.tgz","fileCount":12,"unpackedSize":145031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguyejCRA9TVsSAnZWagAAj1AP/33zxDoJEE84uTBaBYKi\nNWbuXKssv3/dVuvriLvM0FIwzw952sR8krfIcr2sPOoCAfh3XPbwEYVtue7R\nYVZUKteIkPRVzBblKnnDTq1na9pwsD1cb8ZqMp6f6HnqLguS9AihXiT1SFpE\ncF08/peJxFFMzyTb/B7xA/vueBnu4KHHmVZpFwgJ1bKSmVgmTPwu1u3l04K6\nlJBvF5gRe7XzLVmgSIENldPXHflW6Kz7mG6hyN3DD/G5seJV1Dc3qYaOMcqV\nJeikdj3zE+NVyeQpYiNLRNYM+WbUlOo/WsYhPBzqDbLUn1wLMp1mKSdiu7xz\nQgb/mrA+0OrMZZiM/7JizwHR3HiGPafP2xuBdne4gMu5qguBc653N8CIEO9M\nn82aFfEYOewQz+sE6esOg7Ni5mOSzruwiq5bRF1E7rcBDYPXFF4B5LtrHwgr\nRq5IecwMWhNypJm03azdC1KbECWO+eYFWwl2lnQ63YuOsGpzlmLCL+5L0gm0\nEu81b8k8EnQ8SHldvPyxW2jhwhfOt4VHiMiQPAg6sVrtIrwo2cn+dLCLFCeK\nN1CM8sKVxgbF3cFHi0BUY591hbtncZVu68+E5Wil19mepReRiIS3FhR3jg7X\nBEoxVH3/sxuc/8vSNdkDkN7sGW394fX5NbQLqk8U+kp7OrS8oU8PW4bEV2l3\nmp6I\r\n=abSE\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCQycs1barluXEA22+I9p6yw5On8ShiENyLPsfwor8h5AIgbfzueQg3uGzt1+LaBMoSoOGfcOiAcHhmMw0n75dSGFQ="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.25_1622878115764_0.9209511726165445"},"_hasShrinkwrap":false},"0.0.26":{"name":"stagnant","version":"0.0.26","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"78f2d33c2758efa340d42ed7823cf5bc2282f183","_id":"stagnant@0.0.26","_nodeVersion":"14.16.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-7MhLvf4UdBgmDPvt26JE1xvtOxTThCyvoBuodCQF4YwRtHjqaYuZph4q1Dch7geRaQft09yS/UqRppqUxZQ0UA==","shasum":"5dba4253985ac16d84d9d9cf4fdf2426e85ba48b","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.26.tgz","fileCount":12,"unpackedSize":144349,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguy+VCRA9TVsSAnZWagAAgBoP/0Z98NwJcuBycnUNLuLS\nev7e8bucewi34eaF321YOKJ6wsUEUWO5vHJeWr5JBFF4WlzgmKjFYFic3GqM\n7PJLGoR7A2JbTAkQIdyOzrrMUBZUQwsmIelY1tk2oxphmyiaZhy5hcUkFzcS\nBcaUVrBFkiWZIDUEsJXSbjbLXZ4Rq/ZDFxXiR/djnT54zW+arN1BEfgvR0Dv\ncwmWUMyF55rwdpDyfZ9kTXqwzyyOCdWXw3lPB0qFJRXqct4S5m34l0TBIyuT\ndeY1DUU9k/AYBmHuD6TgRU483nW8Y/cwcmnE0d3mmsdpFBW2QP/MCcabEqSO\nWPQ+nSKBoqbnbbW/njaD5Jdany0Xod/xG6s4NSpGDFLbKLmHPAO6YCREbaiF\nVCOI++QpQ3e+f3CukSVhmuYEhSJtSnF3OupXpGqvCmj/mX5MR6EOEHxGYosM\nqQmUjUebzN7q+bdbyfrFL1Au2KFzvJNk1oBGek9W4QwtFed/uUKJsWVpLWJ2\nbOkYBWw1G0j0riV8TBQSh68KKTPuBr+O8Q0PyNM8r/ErUzQn+PWD3ar9SjGK\n12cJC7ADvtKjiRYJRkVPVc5yFXY5eWQ51URU9h2ME/rzFM12z6D5bDq3hFBg\nOCD4TdX8mg/COXNZ2hriGZffth+4wUbrwRPQrw/j1dZ7P7fEtxjW7jyRuXw2\nAd2j\r\n=C5P9\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD+14Z0el7CUZW0WdEUhB1Rw2F9L/zAA+lXZDaef3O8OAIhALxaNxEhH74zmeQYaeyEdAO97lHtMqmZSmejwpgWjJdh"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.26_1622880149693_0.5969333952639018"},"_hasShrinkwrap":false},"0.0.28":{"name":"stagnant","version":"0.0.28","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"d60bfe587e566a9b24572263f827a40abd139917","_id":"stagnant@0.0.28","_nodeVersion":"14.17.0","_npmVersion":"6.14.13","dist":{"integrity":"sha512-R9h8hYKQgcf0wCmlEAcmcW3tyG721rz9tCE0zn/IthRX6okHP/l72ebc6v0sLHKzF9RhhczcY5JqHX6V26iuFQ==","shasum":"630e9ed3f054578206d6cbb9705bbb6665817d8b","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.28.tgz","fileCount":12,"unpackedSize":144641,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwYhTCRA9TVsSAnZWagAAUYQP/1FEoGtp+ZQ597iwZNk0\nH50AAF0e7hzVpQCMetmG0uEjNY2fxcnj4OtBL3NlXP2GGi20q3UiBBfwOfO6\n+bicK5Rb6UkT0Mxy3pCLHNTp6bzKMWcONeJeOeMhVT6nlr849nkLeIKQVVPs\nIGMjKfDYqZeJgeWIuYhhU09RK9CLv85sACdAUSMkh4HTLfMCn6lGdiipO7Ns\nLoalNun5/CsWCuIGZf3Q4vkDTNzJcWFd3UMeK0ghxfqy9SgBDH0tRMziExZo\n5jiI3HwynCrAcwxs+COb2Z8/nhswpWlAR0HwrR1UgYCYkaYU1Z61YKRTWL3N\nNafN+3o0ihRfHiXyB+BIXz2Yz3wMhcAraNSDCQvqQ3Ir7BBSTRxsYDexBO+k\nprWLnF6TPYebwhwH7iHOJo8yEo/2vu92me+Uw2HjJiWW5Ato+/Lfvg/ioqcz\nPQL4t/0mBWui0b8IEGyoe6tkjCnr1m1gAedA6/y8DeVvY7HmJN+CMMXRnp1m\na0kh29gS5lqEzQrtZLYi10arOAHGtCPrmMWKioeNtppeM5wSkKkZuD6OVZ4T\nwmZRG6PAbgrGNKW2TSDegZFkb4HmwgYxXOuwnaRNJXbAe/lh3KMFjHTscrGQ\nkDApVNZhPExmP+FqRx/ebsQ+y+W26Zm0SzdL5tplbUnMLZr/wPENeOg+BdDa\nBjF4\r\n=Z7Mw\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDxsvdRY8deZJ/ICM2+39Z4Y+JYl0LalPSOEds8jDLQGAiB61Lahaid8BQHEjvJbOvKAyKJs+k+NF84k9sBhCIAnyA=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.28_1623296082829_0.9524813681254656"},"_hasShrinkwrap":false},"0.0.29":{"name":"stagnant","version":"0.0.29","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"bafe1dede2591979806be5f5e5160f6f4484e0ce","_id":"stagnant@0.0.29","_nodeVersion":"14.17.0","_npmVersion":"6.14.13","dist":{"integrity":"sha512-xoAuwH398wFOW1oRfsewQLxvFFFmCqlc3kC+nRnNj/nmPsUvIwzTyCjowWzVBQi+p1hwQ3aDosWMZWgOagNM5A==","shasum":"8288bfef50ee92c769cc69bdc117679e7c6bf430","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.29.tgz","fileCount":12,"unpackedSize":149432,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwYnnCRA9TVsSAnZWagAAB3sP/RchynzEbHrMe2PiX3+f\nHMPyK2tKh0s6PD2WcH4G4t53Ln7g0hHmTz+3BDTYyUWWpgjinyslDNGjhedM\n4mt9UNJWj7U21qgdwvO0yIj50P71do4DVodGNLsfXWrne4sqzM5hx41kw0K0\n/8XDMOlRW62qiibjXWlTPuOuFvy3mdYu1r9PafMCAoyLqx7hL46QWUAjlu0p\nfrZYxIvXZ0OeDNbl72pmbD7PxA+68dl2dLo361S3lyXSERDxdfJ76AnojqI2\nosHtXxPVaAVpPSBvbklDOhcXzEsBtOV+sv26HF9bhK0RMhJnSVBALVEYP8eu\n+7QPfQIsuHwKqnBtCwrSiaZmM2eBhkPXkXGcJEdv3LEVkIh1cj4t8SJOBFtV\nSze0RWzH0CxDh9t0ZqxKYq+vXQPUnf9upujWbuMI85WtkZKlJ8d+OW/DMHUk\nS3Tv5X8I3ThK/TktfD3RPmdy03UIFeOG/VNWfR8R38m05fiTFhdpoYAZ6/0u\nX9+1b8iQcFZoyhw6bdeEcTS6LhARvtnC4CDQxs1DCVyP5nHgjTPEFTwUOWUl\noV065zNWi86VE2OrXmB8sRgk5R9yQtFXE4pFTriBa53gx/zAC4O6aiq3nVG+\nIk8BIIqpTCxP7KVsCHuYvif1HoGpftvW0yl90W97vfnNz0wx/2Y7Sd0xVH1Z\nyg+n\r\n=UUB3\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCj6tdULPFWT8ufjvbVL8Ei8LbwiWk1ZVRyQ58HtdcswgIhAI34ZT0BO0miQlb8MdDQF2r2e/gIuyiV1jJjid9jwWNg"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.29_1623296487402_0.9566799065596756"},"_hasShrinkwrap":false},"0.0.30":{"name":"stagnant","version":"0.0.30","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"b93233915ed9053f0715ac2931040d74477dd49e","_id":"stagnant@0.0.30","_nodeVersion":"14.17.0","_npmVersion":"6.14.13","dist":{"integrity":"sha512-4KMshKV0Ue/LmZ2CjbO5QyYYNozzrv5/kbR3xeerj4LBJ3MQl0iZGxGGtJdp6cKUwVJ95fBdzxbFUP1vDXOCbQ==","shasum":"2b239413979a33836fc136d36f3df349d406cbda","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.30.tgz","fileCount":12,"unpackedSize":149763,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwY4FCRA9TVsSAnZWagAAYwEP/3gQLapteKPfRgvsZ++v\nQFfdkFTXvqANdcYNup0VtvliscooVOxEfKjINmkLW255oPuJXyX/A0OKKB1V\nEN4Se501r3lQOhWp34NKNO62T+HfAM+1X7yaxs2Qts4sYqpeDJw9hS/fQNZZ\nGyFEen47e63aXiHyBQ0J3KcAvAJW6CH9ryrEE+Qy6TjlVz9vwuND6Do6sHKC\nB9pC2kaIHbzyeoT9T2m2Wbf9+oc2EMOPv2xzDrqmc3+GcL/oJTf5fKPiyacJ\nVPdotL22S2OpxOyrwrPdAmmTdbiyLAwsZyMybBX0KEANrm2mzTKH8FcNH+Lo\nPyFMPd820aIX2d1ju6lF0AzlUYl3WpN4oiiRL9jbEEehHQNMD9kwhls+r2qA\ns3Spa1Bn6r6rh5wjnFUAjtmJsVDQc6P5cGQV4dvmll+sSD95Yyr0wJqilGQn\ntkIVVZb0GUk8d6gMDuA6G8QedduSJHCy1jFTT4bK78f9dX8YjMl0v9MV/8UZ\n+tv9cABrDD+Ngug6L1d2zzXS8mNYOv0AziTezEOmN6NYyDnCLPBlplN4Wv5D\nIl5cZ62is03c9IhsqD0/PKQev21hr0cvkypZwj+ChjXoMGm5YyTjVwk5LSGW\nXwLi3BPeZXGHy7T7fotf7+F7xDBiPefFZ6Rq+BawIWbLpJwKrpsAINrhm/t1\nFz1Y\r\n=M5mx\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDoGsjy2KyAFcQL35tANz/MUtweHIZW+KJfuV4eC3vnXwIhAN1pK+EqbvxLgEJLtV4WQPtE4Jt37B5K540kSbVoJlbT"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.30_1623297541477_0.48770079543324174"},"_hasShrinkwrap":false},"0.0.31":{"name":"stagnant","version":"0.0.31","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"257ad83866385858289ea9e98858cc870bfd3a4e","_id":"stagnant@0.0.31","_nodeVersion":"14.17.0","_npmVersion":"6.14.13","dist":{"integrity":"sha512-XK3azBu0voG7ZU1MWE9LHmMr2FvclLFMn482tJR1YMq5tHvpiB6S8M9CxZScXCVH1O9M3i6KVQhmFba0InDi3w==","shasum":"1f9fd6505a5f441f865837418b73ae0a5ff966ae","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.31.tgz","fileCount":12,"unpackedSize":149800,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwcFDCRA9TVsSAnZWagAAvC4P/2YP80u0c87ag+woNHet\nEuwizF/k++we1N+ZcxSZx3L3CvYQ1UkooCxxtkggwQA3Fuq1O4UztS5j5ObP\n8oWWFJKawDHLPqrizDIdhgWiDbMNoYHYM5MEvHjPl6vqZVegfOQAyYhSSdPO\nQD3GB+A1tdhwXQi0Tr05DcfeXCs6dWC1tlPB5sx1pkCEfPEOLchpODYkkQvG\nTU40wYCZ/1dT6bgese1KPIe9r1JfeM2tEG7nxH1xQu9BTepyjyKBf5o3e9Vb\nU3ao+lpPv8+8hEhJB2Dii3QCR1GEqzW8Puqd+qeUmlkKOPE9+g0BxFurZUCt\n68wSp4hogVEsbLw6pyMiabJIjXorNC+6VqngsXxD6vlmT+KnlSRoTraiVWUz\nOy9s3XMEdbEFjQgTGD5iKyIfRLxe5vGv9NiCSKLxs6DLNX0gdDZGqOXbHRIj\nSAV8Gnqa58wf6F4NJcsh2okVnxOs/tnPqzOf2+UYwKp0SaCAxzocNt1RzuOV\nrUtxAI8x7P3rbssDvvX5Bh7j0YM4TSRAxjFHIcMhGO30K1Ro/U6fzYFdVP0q\nDy9mc8ZU+mBDTIPKqnJIlUuCRGlnv3mW6qypzUniuYzpl3iapkI0CpNLOl4y\ny7q2ZfrN2RDK6ujkwCG6zqE5NT7DlyfiPw4pcF/L+GnTsFSNeffXxzvU5QZQ\nJ1gZ\r\n=pwYw\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCCdLU6PT2EmRN7t4wI47Mj8ZTjvTWRq1O5KhxqeYnRQQIhANgqWVxAen8dnySSO5oN/ggejk+v/B+b9l/2MwaPipCC"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.31_1623310659740_0.18364477271108548"},"_hasShrinkwrap":false},"0.0.33":{"name":"stagnant","version":"0.0.33","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"gitHead":"60804a6f37d487cf9c113abb3898b38d7090549a","_id":"stagnant@0.0.33","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-CL/57Pbu/2iwZ3ZJ2qJ/W+OpMWJm5OU431ywtdF7ronX8Ayr4A1jHARV8GSXKvcgYcDMEapDTKfv02wqdFURnw==","shasum":"dfcdb2fbe673ae84edb9bb4ecb10f4d0e5701aaf","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.33.tgz","fileCount":14,"unpackedSize":158286,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhApqzCRA9TVsSAnZWagAAMXsP/1SvGvi4Wg/Vi8XCtyCV\n2bYKPbUawUJLUyuTJVJSMLDzb40X7iIKEuy4QLPdjGBiXVUanxLKmjEVIeR7\nRoASTVRkPtx899bMqqv1MYSt8h1cfQNN947bK1Y98ulesKdnJuDFAeD0PsqU\n4cbgmQ5WDrOy66umII8OaSba1WIdV3FVuSerR5UmO8kuk2R9ocgUraz4VXGI\n2gLNCCQjzfnc0KyCxuujTRKfAPc4q68BOA5fqtmEtlaUO1wMkcgmHdxMnwBI\ndcG1HkinDxg914i8cwEefWJ2KUk/hnwK2xxWbtIva7ehfORi+R+sdYdPGCG6\n19MV7Gwwrl49OkabpEyBTxmRdDyVQf31RdMZBb4n//bUUgCwS/sJwOaEPub3\nZL/ZMBaSGN1uUTAOqhS8jHzntGB6rBAXORnM6j2w9c3gvci+douFMYfIfs/T\nAeg3XAbQxcj5WnZFEJME/oTSd+JVRRoFJ5F7RuFp604sX4NSCIdiR0rORH5B\nCJ7vr7XZ73oKUQvFMY1pwb5zUTgfNZUsTca2p3OQjlqaqncrskyBofn9uyaC\n6e/JKXdtuy+hW4sPHZwCcKt+JaUyqu0cZorsjLd2B53Yk0eKpojYolc8tdy6\nY/+zDUDLDvnYwwjDJ0y28vxwy0SfQfV9nhByr24rluvkY0dLyE7TuWqmSgiT\npAHt\r\n=oONh\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIC3Lbx/60/keS8GlXqSY2uKksYM/KDy+z8hlk7YUbwPOAiA3wBw8gc0ULt9OdMHQrCh3d1k5BL0e9QoQ4d8096Pjqg=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.33_1627560627568_0.37066734536949375"},"_hasShrinkwrap":false},"0.0.34-next.0":{"name":"stagnant","version":"0.0.34-next.0","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"4bcff3385c92e6ef9521b23b9a4aaf82ec003ce5","_id":"stagnant@0.0.34-next.0","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-AVra25V6UOvGPL80E2wlelWTBvVOFRQrJQUlmwKsORyX2QOho1ebLsEYlMp0Wa1nidp1piVODTc6jnatNkt1aQ==","shasum":"9fbc3adb8f2ac9c1816c5c6adb9238e080d69efd","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.0.tgz","fileCount":14,"unpackedSize":123503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhAqejCRA9TVsSAnZWagAAWF8QAIXGSQzyxik17cVuIFol\nijV/PIpAPlPvomkxKjdYCiigtj0xJh0hCWusuMdFVzBru/eD57KmvX+GXhYp\n5AD8dkws6rFltarAU7DJaXODGSVvSZw6P2JiDdejIoXFy4bDpLQT8zrmyYu5\nR2LwRWHRROPufmZajG/VGzEeWUUPYaD4eXPACJWt2+/pWFC0zm3exSxzUgVV\ntdlDzaPL3oiHcWakudkiGNayCSWbVBFJ336P4l3XeN2qT9hUvgF/T5/m+2BB\nmrBZmeQpDSO5gUwFgtw19v+xSdhHKTKZea39k7qa95gwuE/5tdgsSVhcC0QP\nFP9K+9Mjh8cnPJYsoqxoL/LisD4QTEhY4Hx+eDkKgKwlBK1wSKT3zWtAQjwu\noUS+fyBqOnfs/kM4P/tQ8yycwV0DTnY6j+K0v3Hk6vxDeLrlr4NZ+B3L7Vqq\nTwEiv2gJUr3qwE3Yjbght/kYne/mIJAaQ99USbN6HYXjf3vllUSxVOq6zepp\nWhvCzoCZb/Rhvh9vqVrq++gRAxoKDmy9ulhBiSmpocFMServGav6dwTz6L2m\nFQ7hVGXG4S2SxBWz3dIPfrUo/vO4UJsP9Z1c5eNRDKbxqkYu2f+dBwQfiytU\nRYQrT2TEdbdwSU+g2LPmiV792MEGrJCkyFpFiS2B1fSmpttXXeyEOnSDqhZK\nVutX\r\n=LQsa\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIC1xQYCWU4OkCYzmXM5Cv3upxUaQqeijFyz6T5blYggbAiBhvl+05HWCc1FA34x44pp8UmZcHFGYMLbqRAPvbL+soA=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.0_1627563938985_0.02909630556312348"},"_hasShrinkwrap":false},"0.0.34-next.1":{"name":"stagnant","version":"0.0.34-next.1","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"a699c6d7c1cd0217206a8ff4bc8e3b18a08ab1ff","_id":"stagnant@0.0.34-next.1","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-nsTwfyf5kjiLJH027aaeph7ILHeT7gN0rLRZpMIAoOekfbQ+QWQRmWOUpRtA6jkCRWZSN/+P7tnuBF0lJg+Kpw==","shasum":"8dd57324b3c94e217f4950f8a7315aa24f56a2f3","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.1.tgz","fileCount":14,"unpackedSize":124308,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhAqqcCRA9TVsSAnZWagAArlgQAIuD7c5BRozrwQV95pkg\nQi66ZrCbh8jReJYP2WyuTRCPYEK+0BbgpZcWxxA30Osz2ZvujuGacuTaZrRB\nXreXv89O2RnLdQQmyG4Ww9Qt/mgqjgf+jqByTZLnyu9/SBkWqNePCrD8qguh\neHLHwpwFdOYmm7Ht/H3CQapNd2ae19vU0Py8Peu4BSeUHMAd1gQIQGOP6RaS\nrrPyKT7a/yLaZRIdPSR0FBi9yzER6pQ0pFnObE7gCCmm5W8zi4oz50CNoej1\nZ6YwBj/EV4jDgeo+L0LMg4sKkEksogecVctVBJXpcGMVVwy9QZ1ZMj1MFhp2\n+KftDU3DnAf87FTj3CD43S/b4qOkGFGrgBxgSrfFgZ1y+IQIUuJewas3pfGc\n6ZRTMH65PmTd4+41DHZz88Ej09O3ivQhUiXU5mENW2tkn9hdNMljc8MtX15f\nQH/qSKu7JAH0UGbWjEItWLHU1GMi53XNn8Ddz5bMk/a7Q8KutBWSwulLZxnB\ndGOfwsSfKjrmX2SjdCSavmt5NfA/ZbsmFoZEoYQA5LA/7Yc0Ra8ymWxuNLLi\n1x4TD4v6Boi9z3ULm1q9Y74NfonyU7NDlkjil48w6cUEe5j7d9VQ4AvIboGv\nFd89SiincHw9wFbUoyddqGC1AYyRJg4aM/PWpkIfVPZmnF2H4G2JOKkmYxP8\nlq+6\r\n=CFwv\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDdKACp4c6JV5Yjv2nH+ZRnLFGiLa/jj+DRopcK8w9fgQIhALPWTAlgiYeKwmH2MD8F9so7mwyOtSnZj2Di9nu6PaUl"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.1_1627564700577_0.9398848295716273"},"_hasShrinkwrap":false},"0.0.34-next.2":{"name":"stagnant","version":"0.0.34-next.2","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"1b3daa2a877662e79befcc15e2180ef425ea971e","_id":"stagnant@0.0.34-next.2","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-pTo709R4hT1LJU+OoVNFaWWLCMDw2T3V+FrpqCMosTBYpOYCC1YCSDHoj4pNmQPyDmDw9y6aWs57GlTGEAG+0g==","shasum":"e98d32867a967f52fd1d7ae6936b8d0664f8d8b6","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.2.tgz","fileCount":14,"unpackedSize":91168,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhAqwrCRA9TVsSAnZWagAAxoMP/AgrEnMSDN7oP9whvrgU\nuLL37jfClh5T1KKBsCMEDERmndoYvORkXvWPdezWFnPyjPzocU+wwMJRLcbS\nzyheyPHbdbuxHM0lF+/D18i5uJAKdqcmDYHaTNQB5Z6jNtV8IcVeLdcirc2j\nNmbYMYNQA9Utf3LeSgFW4riqJc4pHlYrr6WnoMTDaYmVwgs2AvF9TBZUr+O3\nXgmb2qalwoWg1TENsZLPa5uYIPVojfPgekemSOxmeSgRWhS2fyeyF0MaLBAX\nVSv5le5iBla+LRtfnZ9NI+/u2hnWlUUSoRly/gXn/EnwOiH9jkVC+rRROYcs\nGGfQymMCTkjyfFXh2i8X1yOIQwlLNmoMXrNp5mq5jNi76KCBom332F1JvfqT\n3W2wiNoRQykTCubScapR4Tn/SaapSfUaFhMWQUu8MkbTQETi5he180pQPwAC\nIE1ooA0fRl5YDaSkmBVQM4c2xI/NaFrwp6IOPQux6f2Wn2QXSxIWruBpzLei\nCvHJ3ASlSA6Sjtg3RI2gVa40crCnh83gDuuNUXvVlhR0I4oJDyDDxXgRWgLx\n/+9lAvuO2sUS/nlsCCSK0HceAE+69Xw7dSIS1TDHo/kM4AMyQNMIpBhmKLZA\n+Uh6D9d0EZnaT0UBioOk1CY5eMn7/hSQEGyQGBWgfcDgt5kDNHnfZlhULqy5\ndTD5\r\n=+jqT\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIC/+T1Ms0KCD6y7ipt5RdcfXUMz0Q5XRW9jLbCYvSFJGAiEA4WqIxZtIbUGGR6XlbP35qNda7xMlopHFWBmTvpxEYro="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.2_1627565099123_0.9473148296482212"},"_hasShrinkwrap":false},"0.0.34-next.3":{"name":"stagnant","version":"0.0.34-next.3","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"c352c78e5e18afdb9c4634dfddd8275b28f07ae7","_id":"stagnant@0.0.34-next.3","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-aMCiRERDAlthqA/L6ilpjDvuPWPPqBl8g5VrR/AGyLOrWP2zw2J0bUJ1tdph5Vvz5zSmL1KE87OxS153NHxujw==","shasum":"db68f10c720bae787c48381e923e8be7cd1f3989","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.3.tgz","fileCount":14,"unpackedSize":128816,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhAq3JCRA9TVsSAnZWagAAdvYP/ib3vXehsCVGwJPutm/1\neQFHtREu4r1Va80Cv0aZUFu6qQ85UvV3BdWk7RaDzisBVC8YTWelchNx3NOB\nITGjOhVisTmbIwKi5Lf+ey1xJe0tCUAJusCV0PAsOwiSgAEcQW8JXS7ZaIW1\nxCyV8Txxtj3wT7CvNpAGXjfBspXX9VujPP6WCENFGPNqa8aR+irQ1FS1x7kV\nagKYmeKMO4DsM57N/n1aoRbihhKhjc96rS9Po8Vl97n9gmNcDA56xqRszQx+\nY09VaETDo9qLSZi/F4JOZXb/mUKetC79RJqXuXsMTC+Hkg6JYJy58qTNeFWR\nesABEIPU0cnKqbfJfWZu0DOF+zaQGbcz/eKm3Z316z6idH8t88j3y4XtSnQm\nE1QRJ0qJj8fHyWznFCbQ1ABtPfICONG4mWvvxFjqdAEg3kqCT26uSLMm7MH1\nx4fWI8PzOL0mQPGBD4xIbhJPW+7uKP2zO/j+yAf2zXl5bwiU7n9CuhaFCdVK\nie/fytpkQ+tYW+aqypDXF2uVNpge3Gd4SfWxudcgYOU8NnV5+jLSfkTZbpdQ\nnI0oWH6Ld6f6u/GSlfVTXrWxhvGnHnJYRp3/L1N0pIWu/s/W2gp1ngdUHJLZ\nbJEHO0d5QKN6xbsPOc8/obC3+ZzH/ce7GkDH1CQwu+EU8zazGgRFtAiNHkSx\nKH93\r\n=GemB\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCQM6miAlbiKRvXpgaxjlcICHYYkHBNyPdOUxszsdqXQAIgHkQi3cVcoFOjqdXejKNZYiNBbK4dKvSHa7ulq3AR1bI="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.3_1627565513107_0.8901740093519399"},"_hasShrinkwrap":false},"0.0.34-next.4":{"name":"stagnant","version":"0.0.34-next.4","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"d3a9f4593e7eb82956aa7620c151bbbf4ad1b173","_id":"stagnant@0.0.34-next.4","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-esMtgf6ivlr1EMNXbXmxGUK5ayRLwzUt8mDNRrL2QgIonE+/9g8xqougte/WPh9H51xpaIh2wGgKmQogrQo8zQ==","shasum":"ef1e6540095845dee591b44212c613b72ce055b0","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.4.tgz","fileCount":14,"unpackedSize":135002,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhAq6tCRA9TVsSAnZWagAAvIIP/3c/EPW8bmPG9MMQ9fdU\npFCTCXs4KAqcYbMnmOt11TsE8T8ePE5ihbDhqzTqnvQwSV9RaEbna2ckCH2c\nV/uCttiULZXhK5Qx+19DyFq9Uu1Dg8WhKlZqAJmbg6wYXk83VTyO07u7emTa\nKm7Og7gr5CIVrTamR83UzHU++1P4rl1Ozer65s/vs45mK302uzKMACmrD3iv\nMlkr2G0+PMgDWbx+2PlBWW9AzY8kSDrOb0Y3UtV/JX78qOYUJlpJdyAnLzeo\n0vHCOBFQ0QVfafxTysV1tV8WoxSO7Ed2mqnty+PKyHP268hKWs0iyyY+EYJD\nIAdtxFMNhe+I0FzDpgJbyKE2FB+O76IuPLyf4eOCJcEMOTxgErf+XXSi/QQe\n84m4TzNz0WFZNF/JVTQIJl98jMNxG8v4Vf4Jn+KcvgOgNrp4BjwHvTFt0Bdb\nlhs01WhN6Q6VBh10GFGi4cCImG66zQurAHPTrvoVSedSvzVNBXo0d8QHG8Jy\nPM/oLSCPtyhrI+mUnjVhtFetxkhvaUs81NE74aTL49hri+dVpQpeFbhh9TR8\nG3XVIg/AwL+rlvB7nxyVfoRaLyjz7QZubE9hnlMMd6oCMl1KO3ip8BTSRUzt\nTrRXuE0LLV8W9+g+ylbnEBJ9yAom6k6IbtrGMvDtMsSlqmRsU4rmy/TJg61H\n7c8Z\r\n=LtOG\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDZqw51frQi0HxvpS7sQwSSkVmpoGHv1sP/5WCiy1DFhAIhAM8kP161pTgMbgodPuHG54rRjFVrZtWhtM4p9P4FDvC7"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.4_1627565740806_0.8413596456808801"},"_hasShrinkwrap":false},"0.0.34-next.5":{"name":"stagnant","version":"0.0.34-next.5","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"7addb0d21aff045f10fdb3c901d6e93d40ffa78e","_id":"stagnant@0.0.34-next.5","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-enWf1gWCLXrTaEYwfhLlyS1GwHc5ax4Ew/5YAnVEoPTkfRWFwtTEW47j1m0x3vB2dk+/7vlP271NGz9eQdLB4g==","shasum":"d19b36b2a9ac6110188b6d86796dcb8d617cfbe1","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.5.tgz","fileCount":14,"unpackedSize":136059,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhAq+BCRA9TVsSAnZWagAASdwP+gIBWbjPIJhrYTdy8D30\nNzXSIkFiZ8KfIQBieDKmLDSv7JVRsbdbHsleeYOkm8zwwiUltA3LGwz0Cj2C\nDq2qGqemLLaLNrXWpiYF6Eq7HGAjSiFVwHSmpIDW5PzIihiNBuZ2xVXWlZPY\nXGoKzKxIXNvL5vi1nZVzLVift+AtMWt8IJYDlIlZQAXw2i44pvuNKE4bNVNP\nXEnHygCgiYrsy008DBDem+9q8AMOayZPxbB+fKvtwReTWc0Aam7Gku7yPTF5\nE78nYFup+dKHu7V+J0TMk9MwccEvhdngLUIut5PM3p1IrFRGixNpIlFP1f1u\nBE6WoWQryk2hbU+w++WW+Bqvbi3shV15C9QRF6vGuq3F13WBEqZ+9/ZHKru8\nFfMt6YR07QID4jjH4YZXMzWtqsr04O2vei8pUpnpdl4dedgFytBw9lpX0AMF\nUe+2vjsREXq3paXyxpenk5GzhEhp8Rk+9JLOtTUv0R65Vwss4yYMqZ2uLRjW\ndaakrrIQWaKbO8UpuSRS23RHcbzt6yWvTHqRyBuvNo3Dhn86unbo0pWPLi0C\n/HfaG65BZfM7a51Ue25EOy+CAgbnsIVeQ4+piWwEny3lABVyH3HDAROVDhr4\n6d2K3f2L2VjOR3/fMmLtOZLYyRl1qKN9Ote2fbszPAhu7D2S1ABGAzmULYGN\neT/7\r\n=KlSL\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCID1mXFnQ3TlRUcdkoKDFWxGGUZMjwZurximz0RZ4bOuUAiEA5xwPJad71RjVSgTjFBXC+57e0TzpRYuYMyiYY98LxGQ="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.5_1627565953045_0.2883093177591871"},"_hasShrinkwrap":false},"0.0.34-next.9":{"name":"stagnant","version":"0.0.34-next.9","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"935ea0e7004bc09b98f226721bb57004fb582a0d","_id":"stagnant@0.0.34-next.9","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-IlQWKK16BPvHwPbZecBYQdIgelheOftBHlIwdQoeLvVK8QQAK35dao6UowWsx2dn5TKYGF6Pse0w2dUwLQRicw==","shasum":"ab0f42c29ba70746f4de5389777e389a96113c13","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.9.tgz","fileCount":14,"unpackedSize":136836,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhArIACRA9TVsSAnZWagAAOw0P/34pjiNwrE/ejjl6i3bx\n5G4vl+YrjniP72tDeY1XAlC3OVHpIkODBUKkW/k8rCqcq114JeUgoW5r/Xkc\nOqnYmhUCikexwV1QOsMXNC3D69VoDVj2tijekTeJbjFrvaxpr13vR1hVI2cv\nMcFVSqJmIkY4NLn5lPxXgyQa9ia/IuXsoL5PKOSKpYIWIItFSZXYn2+iN3Hk\nRx2BME9q0VH934yuFVey542VwX5r9MGvjnAWemF4Zn98KJBgZpGJ7EHf9SxW\n4USI5gdDOXieyyULFSvGWRYDUv1+MUORvFdFNowiP1axD1PEr+pJgLd3wOlY\ndtw5QBernALN2GwubGzeuTQFFRIVw4YAxEThZDlmFR3WEt9xb3PlK+y0UfF7\nBVFcO6CTg7jQSdp0gCD6WpP3tu+uRM29jSCAj3JtdWtv4b5ci0jS215MlJ1U\nHfPKrtboPXPsBMZvkQxKTo6A//z1HDVYqNvxhkgdNyDqCtBwFkEXxuEFpBYa\nRESETFLrfYeERQXkj3ZiMiSJIaVHJ/LDoyKeBp06s6m7PTw/seYczh+65sg1\nalP5Cs1hKv6nk441RXH9S3tSZxUVa9gTOowM3d09PjHfIJbMWZ3JFz1D/nuk\n+o4bSnm+KOl0H0o1T2TNIPwnWHVwyB4SU9cWO+bG5O2TEFIlGB5SyAE7Mo8P\ndv08\r\n=RvOb\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHopJvTKeI/Pr/N0kNmi/ANTLNawRu0VN8JDPGDAo3hqAiEAz0SG8GpS8YuhsXlHkAJBUeCvUHgFb0l269pXqYI6eYQ="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.9_1627566591970_0.5562510713213427"},"_hasShrinkwrap":false},"0.0.34-next.10":{"name":"stagnant","version":"0.0.34-next.10","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"d6103e96ee586a93d7977b25f2169f26c4a97255","_id":"stagnant@0.0.34-next.10","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-HXUuD0V8QKcmbsZtC1S2bYZE2bB1xkmg/GzhEK5abOxUldjXtDXfaFgyv28iwNhHBY59C6pV900p0ZoRf73IZw==","shasum":"b5bf6dd19c1b0f30bad9053ebc9b58409b2c386f","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.10.tgz","fileCount":14,"unpackedSize":136933,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhArKiCRA9TVsSAnZWagAAIacP/A45IUjbdWbdGGZnnvNl\nkS1lq3RCwW/N1MxMJiVhdAmhFbap9zSr+RHycUxPgrmYut7jGb7z4DmNZii8\nx9KnzuZ9IdHZUKA0rCNo3i1b+F7g5Wb5CWMPiDYwiSsCvBgsaVk6K96q+7d7\nPMVodnE8Nx4faN1UH1nec8/q6Fem01/JjJfuRV5sjYw693nnWFTBvBQm94lH\n/pd4RpTFrpnKjLH6ho084t9LRMpk/DJXujpeMRZQiAVljuLivyh7UFGesxf0\ndospftZ/FSAu1NpV750Kxm7+hNiKiSbFklL8Cko0UuhWn39b0vHla0EZ6+Nu\n7Kf5QmSeW7SCC14TgR4o3roXBhJhQbrNYeSgA1gQJWrB9FcDI6wF+bo/01hF\nxKLccAulmU8eUJPdSN8OTdVLRpQPNThAHh8eZ/MGNXBAsy9Qzd+GsEqG9TVS\n2DyibR0l9/vs1QO6Y5q8Shd7Iwh1E6rjwVY+d6Q7CAdxd0vm9b4WJmoTb6o2\na1d6hccz/Yd+WRoyAYixPmOVwNiIEmfbhf50wc+aVXw+FAEPCMf7vxRLJfr5\nYBaq8At46pp2yhDRjtdgaU3ZTYY0JOlCEEjx1Y6WBDBmXgtliq+60tuRxcdU\nMXHrb5pdn3qY+WOU/itDj4/ey4vpjA+lav9SQbkl5ATXlOFlLQxL0OsgLg93\nHYVu\r\n=iSrC\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCD0+lH/3s0QRdkz3cVhho9N6HAhFFvSyzshOVxv25QfgIge57wJEHMtl7nQzx3dqNAuitOOU4UhjFoLGCL1hY6L2s="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.10_1627566753844_0.7428143647139887"},"_hasShrinkwrap":false},"0.0.34-next.11":{"name":"stagnant","version":"0.0.34-next.11","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"3dd7d677a996fa1f2ec6ae7f431830b05ec3a397","_id":"stagnant@0.0.34-next.11","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-43K4RCRFSKTJKS4NKN6b5oMNqMvintdyEsb6rECehS75BbD+CuLgpFUq7gSb1wgzsyEOHurZ9PvV9rMF8gOC6g==","shasum":"ded7fa6f218e29edc5457562074c13f8c9e7a38d","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.11.tgz","fileCount":14,"unpackedSize":136933,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhArM1CRA9TVsSAnZWagAAEm8P/jg5+F0ILMSAxAXU9r7J\nIb5R79QYVQCt4/+kXjg245y31Zxr9SSguLFIoxpjJng9y441e4RmTzQe5iV9\nHFYT6OZsXeLEQDipHNNrCqZmEn+iu+c7QUS+2oJlFaxCwuAV7/lWlCZRulBE\nvuR/8adQiwaWwioRl1ZEh8hg/bWW5ep1dDjcphOUPAhTQcKDUu6vC32b3tAp\nsl5sqOC2HDpJfpUpMN7WrRuZk8uEgqOh84I8Fidr78K/4M2+WJTM2CMW+9hR\nRvT+oqV2ixCT/a65vFw08kYcYFoI0BJVJXOSEqxRDwiu+uEWolcgGA/qq+hm\nlD6Sx3G5Xfb+eax1bZPe+48G7qtdlaM0/j/jo2jpx/z1f2UwoUbU8QB0NlEd\n1+n3crPYeSilL7Wdg7a5V9r/R9x7i+EDkWrnSOja64avhypaVzDbRvDTfMMb\nfTnGgd/QkuvWlZ6/BaGQjPuRbp79ZzAf/vcETqp/YBVHm42uF24UnLD278Mh\nlKCg3qYW6gq1iAVGiuWjJYA5eZK3wGTeuwgPqL8FuB5v6rexlYOngj5ML8tg\n/YvImgQ1i4umncMFnSiZqzwYq4NevprStQym7YAl0GqboW2PNBFjGYh/nirX\n7PdPjuNOcyrxloOCUlXgZ6O5urfKUCQCs+dGLQL+yIorOc7lbuDCghDmywNV\nfs4V\r\n=ikDH\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDWx48azuEd5P0EHbzIKj10KT7BqC6fqDftrEADW4y6zQIhAOauTZE6HPlUqJKmZtiCuwO9L5btQOrSivY8hCgtcPFA"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.11_1627566900901_0.8242964987835348"},"_hasShrinkwrap":false},"0.0.34-next.12":{"name":"stagnant","version":"0.0.34-next.12","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"0ad9a0297f9f2a78e63ac3d17133f9ce03b192a3","_id":"stagnant@0.0.34-next.12","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-o0FDxiencxKvGYoKud3ugDLw0jG3xV05B6uNRd4ME7IYJQ8f1rBpgRvz3hRn35Ehmvzu12MCPj9yvZnNBUaJMw==","shasum":"38ee8b4b9bb0460f9f3b46fbc653ea428103b17d","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.12.tgz","fileCount":14,"unpackedSize":137124,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhArSuCRA9TVsSAnZWagAAoI0P/R2cTpfZiNB/ko9a87gE\nbAStKI0EK1Up3zkVBUcbOmEmvWDygKTJ7/utHW3ID/kyfFcYiK5JDpjBLArr\nQ3b80BcK+Gt9VlTsDum9wxBkeFl00KZQHbumvxm0gkZmau4Ct9DVEyLMWj0z\nkLzW0MaxT7eatL5PFCb01B5pzknmSwfSvJvH+s2FZTZwpYS+oGo3NtnXA862\n3UKaYyKZOvrADpn87P40VTri36jTlXZqObJByoJ4SeLE2sm2GgLqOaeyL6rZ\noGYT8a2YNLInCEzP/OUogUnQMmksmVECIiw5TAdyiAFUjrVrhTyWwZ8LaakN\nTLxCP5snY7ARzQzr0qQ6zYg6owmryMe7/o6/tlkjsrebgCZf3lAG9DdGbuIX\nIsebVz1kTsiOX6DPtnbxhdUDqoW06xhqs0ie/bCfSno9i/rofn3GAyubeW+6\nuTOIprU2P4qBfNzl6XqnpTaJgUhYgQWqFPYWdP+Fvdd/ChY9OKWbld/cbvci\nb/KS7A0j0XaYshwDLSpm5vl0JNJTw8e94szDaVrZzeiEsi2dj2rkOCU2aQVT\njkDXGkxm9m/nnNqpa/W6MZZwktKlA0ZdxI4DyAn0Hc5SIUpXmeY6fr0nmdC/\nxWykZe5I2Kdzu5Cf3PJN2b8pF4w4PPU4l4i5W+A1fFSO3CuMaxVLV+ytyeTm\nZMFz\r\n=opoH\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCr4oeK6nsTDv5zhlOYVTHsZ1OHuQgfBM1u02Wi9/2HRgIhAIuz3mf8QhtKVhGrGvDbPKU3yBrwnvL+VBVCuUsO/AJ4"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.12_1627567278316_0.0485572901613589"},"_hasShrinkwrap":false},"0.0.34-next.13":{"name":"stagnant","version":"0.0.34-next.13","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"2775017bc5899ca88ba6a3ba9f545d864ed0b9b3","_id":"stagnant@0.0.34-next.13","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-m7Kd7P+dm9founJ9X3HFPeB9fY6XF4VJUnMRP/FgkqhUuYHitcJehSx9g+uWCJSGtKMy0kymVixADf8yUPzUlg==","shasum":"046d3ab6f20fc3b3ab578bd867ec03de895cad90","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.13.tgz","fileCount":14,"unpackedSize":137468,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhAraZCRA9TVsSAnZWagAAFqEP/Ra/kRzXX3lsTcGQHfOv\nZ/5hukD/E41qNFqA2K5Csn2q6hwJvNiiyyS7A1csLojWQqwfV5DNRsix05OW\nlE1UK5FhInk3fyADPouOj7n7KQ+9Dm+h8Y/03K+a3fp7GcFB8gYroZwjcKJI\nKwB81HqkWCt24k5pVgznJKSlE7b6UTmE0ySx4lCeqP5CjlvrqLR1q7nnoHj7\ndBWo+SXhTeybU3CyXdfB03KYOfiOCVDJFGGBbDOMop+uOUiWaEGQHbGnbTBn\nXf3HPVc6OF9JTlBVZ8fe6AsgSPluDsX1IAgw4zHgh+11vSQ8aViU8yUjge2k\nVJyWf0Eod+VZZtikfshQJRXb6uV6wh0pcU3AY21sIwVc3LzVySAp/+P8r9s3\ntSqbDG3WPU7uKQZjBgPlBdPezSe0EyIcZrBazRsmB21Ct1ZBnz9GYm0SRbUG\nhdQJ+QDIOZAm6+PfyQB4J6zuU2IRgWi0BvqXH32r8zWI3sRNZiOcZZy11wFt\nz5RmGdLws7pO5ybkOFMKrM6qYO5r8ns22QLKrjWE1wGjmkQTCNGovua7yDP5\nRggUUUAL+UwR6mc3G9cK/QHg+qPhoXKnLeTG7ysALfSTkqpY4eW6uOCE108V\nymMo+i4/2E6kJuBxzzKxqSrxBoynOZuKK8f7qOlb8+tEhuh4shConJ+gHO/C\nw0G+\r\n=WCnk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCztTCgSsyqR5qUUr9Ne2I6KEsWOC9l62BVMSXNtKH+7wIgQjStKMBXdDabPo6XYOrL0D/MhsjVN8riecXWxpAea4A="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.13_1627567769403_0.803566785167396"},"_hasShrinkwrap":false},"0.0.34-next.14":{"name":"stagnant","version":"0.0.34-next.14","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"be90c6f20e237e6ab635d9f0b67c3949e242e060","_id":"stagnant@0.0.34-next.14","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-jDcDHHhcIRGkpdKDsiK2vSRxG9PhekXQLbDOl8+xHa2zUHzg8xxjG9sw6yLPKA6v0fN8Xq2dPdNILxMOmLd9sQ==","shasum":"aa428255626e253a1f248b4c0bb5bc77716dcf26","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.14.tgz","fileCount":14,"unpackedSize":137908,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhArf5CRA9TVsSAnZWagAA960P/jFr+WgjBbpYeTynizXi\nkaAUoLvYO+joeHXH6mNaURsPDL4zR7e0ebg1pYq+y/bu9NnnoZ8DpiNFY5pT\nUAz44gW6qlHfbwQe3ar8ARWOb/SSwDuiU6u3dTpBk51yXGgXQvlExgNt84g7\n8tzMPLC/fPh2jWkfmAQpj1aOKDlBMK0lBhAZRz7EZChfhokmHTZ2Xp3i5yUQ\njjAL/1CuaSAGREXKGk4t1wGNVlf9fflgXwxiZNVbsVdBmC3+/AwYRzv6odl2\nffh6matxDEOUeVFTU3L8y75ZDYOGJhghd1YPcBS3pM1xUOjoPPzLCXwSU1g3\nWA9Al6SxuhylXNb4FqI7/z6Sbd2WhKeLbR1lrPkb9jcyczujeJN4P3360AyW\nDLLKyKsTqIxeslWKIEvT7Y4ICZE5ZMYuDuMFPbrciWwFSgC4Jy8oshV2U46Z\nBoY4d2N+vL/Ko/Vsgz9AUV7xh3kmmYZv7cokyd6tNyFiKIHkLCmzKl7JuVXI\nXo3lcia9m3cugbpE9jITYgKu16oODAg4OIicYuEXiaUH2j34MbJtO7U0vvdc\n+evUyoK+cdhDzA8QXxvw/+McKZ6lBdcFHoUkpyszvVzZa4i7ig6MmDWrUmxE\nMAAjGmcbwlB0rYbYd0qY9/hiegqrMOjLXFsbix+DBv+qCCN7V5EQ9kQFdBsK\nE9Qr\r\n=xnex\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFii5KPMrAkg54jxXl2Pr9FJMhH+ckaNeviC3B41lXlcAiAauZfGaY2IyU8grTtqx/qlzSa5kmozNByEuqEWZ3a62A=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.14_1627568121361_0.17634170873959287"},"_hasShrinkwrap":false},"0.0.34-next.15":{"name":"stagnant","version":"0.0.34-next.15","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"a8f602f06caa797e027a7da1343fb60339f6e917","_id":"stagnant@0.0.34-next.15","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-AAm/p5YWGSxrJVTn091RflPOqQAYbD0oO8PF/EqUmYXhALnjANE7zHvVWRnd2K5BxcvldwR5xkvYECnBBownWg==","shasum":"d32b80b022d85e400a2c15b3581c8956a7851692","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.15.tgz","fileCount":14,"unpackedSize":137519,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhArmJCRA9TVsSAnZWagAAiZIP/AiTMkHxfEq1Tm2KXaiF\nOBdULp+BEEhJNyPxemP+aFIbIvTTR+mI9LeNbPWM1jz5GM/Ec8WLZxizkv9T\na3efmYOWSAOQI6AgJLY/9HFyk/Q2lEDdXIlUJv4k7+YJgJyMViRtKGkHykTq\n7zUSEeCfCIunqT8cODKHRFHR/NgQ+9G7qrjD7oq8/obIyBIoJ/p3BNaF8nqX\nsDlygp9WOKNbEboUATaWpn5jyTkZ/mpindAIN5llDZTZ9bVYLrvLAbguriW8\n02PFvOKOEHQVW08MdaN1I+qICrgD3pqxSHCZa4TbFeRcbQfi4OLJUZyeJISu\nYpCGg9YEFUNwlr0JmmiaKsrSkHZ/rDjZN55JsRAovzTz/da/NqzWzqsLvYtJ\n5K+qQ1ZOTizJl5sat8gQenlXv2haUGsne0o3RekAtVP72gyGnccRmh/4p+I7\nfe34LtDGR778HKuAXPjoL+2gmQu6wj34rudwpyWjkCNpL5MOJ1tTqcH9ZMYC\n+MBmNzRcENCHckfFUT7pNWLhLj8a8mKk+RrdMdEbpDVo0QNri8F/35Td2LS5\n2MSml3QAac4EO2KlkQCfBYr5VaOGEJt/vdfX2Psi0mdVQ3WHY+GccS6OdXfH\nDOm23jksbaqMMjsyGjVp/RHJlj4Gw2qAx4bpDpIBuutwRZBOgLOmwCCPK0Qg\nE/O9\r\n=ldES\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHL7UG31vTZsGESNiLzISeHaj0DKiEnLanFUEl1nv1PhAiATc/ghzRfokihGFnuDqkzVgb3rZXyOPjPJiY/0Qxg29A=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.15_1627568520947_0.914854207854155"},"_hasShrinkwrap":false},"0.0.34-next.16":{"name":"stagnant","version":"0.0.34-next.16","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport trace from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nasync function main(){\n    const span = trace(traceOptions)\n\n    const package = \n        await span( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await span( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = span.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await span.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst trace = stagnant(options)\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### stagnant\n\n`stagnant(options: stagnant.Options ) -> Trace`\n\nInitialize a trace.\n### stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n\n### stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise returned from that callback to settle.\n\n```js\nconst I = stagnant(options)\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst stagnant = require('stagnant')\n\nfunction query(query, values, p=null){\n    const results = await stagnant.call(p, () => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], p)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nAll signs point to no.\n\n### How do I continue a trace across multiple servers or contexts?\n\nIn your `onevent` callback, pass in your own `parentId` or `traceId` from a previous request.  When stagnant creates what it thinks is the root trace, it will leave the `parentId` property null.  You can use that fact to conditionally add a different `parentId` from a previous request.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"26143c5ae7faae6b25613a656432e142ee1debd7","_id":"stagnant@0.0.34-next.16","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-a5LeQtbcGMvCLq5+Usjicjmb4HAqsTiUVayAL2Bu5k9rceWdUoGEUlbVx8oDeII/GPdbUKJo0PlofBNw6KrkyA==","shasum":"14808e6d5d1cd9e37b26057b5dd04a45c775407d","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.16.tgz","fileCount":14,"unpackedSize":139012,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhArxrCRA9TVsSAnZWagAADT4P/0FZaKULYWFdGgrfThZW\nlqdMiqofljAsFKSDmn3AK7wX1Xg4qq5PNRHd+PYnp9deaO2eN/SOrFxOlR4X\nWB41zR6hUilFwHLVPwnCG+v1mgcfhh0jpLbZSqWNDZ65eA/mTN4uhEWIvC+F\nTJl02edQt7JhMY6PTpI4fq2s6wHu1n4pP2EkHPj/9pxFhU6EvQmKYUqrYHfK\n4BWTLxXTLuZJa5LSL6lpLj8gGB9P8PQqeB3kH1DAWZ5qsFEGOHo7BjFh5U6K\nzbA/1mubgw7mbUx2oDixnCybDr+JIIBoFW/4jVfzqHkm2oaUHrdBZ3fHMS6s\nQbrvmF81rvx0hwVM/4T0srHg8HrJ7J7ujn3D7VjWtnsRDRWHoM5+bo4WqW2K\npWKYt+3pS/yXMMt2usL88BW1ShMyz0Z9JHCEFtcTCJ+SPlQT/Tu1c0tW3Utw\n2u8Ikhor8cIuKN3MpxMELHYhqEqkCG3BgB6J2vWE7rWBSxMrejAGxgNJXGjM\nBl+vxSS3W37/h7TzgIq7Nwjn50WG23gXFb8YgX1HCvraHh4Dvy1XdfcyvUMN\nrk7BadFSm1rVZQtqy0/6y+/ycyk4BitHrJWneCsMqlqxMafC8Kh1gY7OUTQO\n4aERQM/BZ6L5ksF/YnFBD8SBUNs20lkpFLl0xa/E5sgbSBPGEdfe1gcGq2xO\nP1Lj\r\n=sFbp\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCJNOYS+LhluN8khsiJ5TW0vq03tLCrJJVZ/l7Yx7GC7AIhANDkKlPmLtwD9Ie4aC7fbkWEQRGzXex6UtFaUmMOk7Ux"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.16_1627569259512_0.4393465982899931"},"_hasShrinkwrap":false},"0.0.34-next.18":{"name":"stagnant","version":"0.0.34-next.18","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"9093efcae1ad9a33f2fa22f8d851d4549925abaa","_id":"stagnant@0.0.34-next.18","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-G53mnuLASXkaWKp3KHBVM8SV5DZICSImdZj5bWseSSvjhKc2VvM1/owHqRlfZvE3huumepXdbVqFKwnoYUnk3w==","shasum":"48c69f7b5a5f3ec8f5a14431b0af05c8cc253e6a","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.18.tgz","fileCount":14,"unpackedSize":161837,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhA6zlCRA9TVsSAnZWagAAxxAP/1ldFd3tBYyOP8GDVdiw\n2BXYUzi4sKXicWFsScA5iF+68Mza+w5RxZMuqMz0ESKF8X09KRzv0rUoH39q\ni+Euiu5fxHPw+VZ69GtV9ve6o8t3fTKpfPatf+xybt0JjMSY+N5WhFZm4scW\nkFAXgiet3VAZ3A2D5JFXr1IMis/3WqFPGUHxFVzg7Sz39WWE4jzUIBXbhrNw\nYGgSOXcfwSRxwcnkFbdpq2zeiiW7rpJq5g4Fo+jE8Z+nlF75FFWBmbBCin/D\nYdSP3M3S5ZK3g+IKC2bBwWPmoSkohV8Of8U8znzmM5J/wO1rFCMkTdGFuHVW\nFgGkbFrn4uG49f8D3UarCCEfBD4YKI3XhaOZEAHD4uB63sroswVzoHwoyMpu\nvJGbW1vEp9VVqNwz3uH8d7ti6tnheTYqb+sPib9zU/pqjtWactgB3KFG0kvv\nKssDHa/2fd+nOyvnU3VS6RcDuP+j0bwtP3TjE7qFXOU7wg/V//uJEO6/m2/g\nMPdp0qLJ/N89LV/h+6UV125m5sm3HxrVgsbGEAVWN65svaU/Z6OIf3bSunZ2\nn2vlR72uRMqoBej8CgGYgAB9lE0qfVs3dqBKx3fqPS7zxubFw7kMTY1F52sL\nZwRmOzX65AvCE+Ka2DQfj1UHkF3pliCS14c9V/In3e/dHHKafpL5oF5EII0s\njqkO\r\n=qP4e\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQD46pYJ77TJKGuadbcT2zaI+hErx6ROq8K6FPD9SawncwIgBaSaU83PP433vX8cngKKVhA/QBa7EBcR7iDSBy2HquA="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.18_1627630821768_0.8192027175864383"},"_hasShrinkwrap":false},"0.0.34-next.19":{"name":"stagnant","version":"0.0.34-next.19","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"e908d12434298520adff45bfd0dd22a2299de838","_id":"stagnant@0.0.34-next.19","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-Wadl/kPl4QPZXcRUfhNYVT5AcI1ZbRa0T3a2JAm//lxVvD2pGI4TTXGpNzy190v82zjytAKpyiI81hy3LmoODQ==","shasum":"9c677d08916d9cfdf0cc9b1c0bf13a13ef0043cd","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.19.tgz","fileCount":14,"unpackedSize":162809,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhA655CRA9TVsSAnZWagAAMEwQAJYyzZAsTtAG1yZb9Ww5\nalRTE+Nr9cRM0/5Z6N0rCbV1mgfBj8Su6TDV6Nzb0yuerPfR2tXx4eyTBxBg\n3xULaZJPiSjY2xlTebBhPWiVoMNb5Njmtbi+b4GrJ4XN6UkOyaeRao6iHEZ5\n/zOWfbjbYvZ+mz1/ruItZTKYO9ZEgC8KasfEsPNOyeAGnjeNxrTAMjGDA9rO\nbWcJX7QSn6fBVVQKc3Fu1J/yibC7eb2Pt3lUaRgdmzLio/zto4yS96FrP3Hw\n0SgyfV9Q385suZeSvTknlys+9E7dTTjE47Ke8TeVPKeimBRbAIDZYEFkdjEX\nTXu62+O5OvCfq5O30DKlVHohzcFcGaq116DDvWwmqGZNfFQ+V9RLh/P7OeQK\nJOsJ32hcMmdjKHSIYV8/Dc2r/0xQaVN/Rvi+XY3LzhtQPRTBljxQQ3b1keXi\n6hdE+TTsXpo/FVSt4qhGL/TLHl9EYVsVd7c6sr09p9bdWzUhSvJY5uNVtcFo\noa28PD1YFGQXYu9PSjw174G/4kQQf8FJUOgQq+BWQ65+isF4WYCLut4sBdgu\nG0uAhFFMsKysKYlu5RMyCs0V1iapNtWBwU6j7PLuy5lJoEwlacT3VdlN4QKF\nMFfkmXLUz/azhLPI8KOo3lq7hOdskcveygXXFfJUKOrbz2AMf0e23P9OGPxA\n9gqb\r\n=H8aj\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDbpRVJUjl5+bVquI13oZAgZUBUmM81KrsCZwbwfqLgDAIgcyrvEFea+wy3um/jhF5h5yu+1Dd1/xEDi2jRwSXK0GU="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.19_1627631225689_0.07267027464841047"},"_hasShrinkwrap":false},"0.0.34-next.20":{"name":"stagnant","version":"0.0.34-next.20","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"1156991c5e4bb9b6f25b1e4dbd7d1078b07dfd30","_id":"stagnant@0.0.34-next.20","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-8zwOycaAVxduhxtbT23yz/HLkQ9aMRIh7zXkHb/Oz33x8mGd7OUzGnRYtg1vFRzjgPj6AoRE3JniyqLS0v5PYw==","shasum":"afa575b7a86b4c2476890205075fb5199f142a59","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.20.tgz","fileCount":14,"unpackedSize":163851,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhA7EqCRA9TVsSAnZWagAACSsP+QEOAO7pJ2puKpY/LReS\n2lWLC0VkWcXJ+p3/AL9sODA/393Ka90UxJt6LG0QqRs/Cw4Xgxkoyg77cxz2\nyuE9fBoEOAbHdzvA0Xt8nozTVax7+HFSJEepS9Yio+uk+0HzI0sG4B2jk/5q\nIWKYk4aNdr5VNgNdifKPTzlaTvmI19R2UOeFvq2vGxJuYR9UDGLkiR+WjS2A\nZu5gbh8GT/BhQL/CPg9MGBECuDHM/JeA1vhb5Hf1xiFd78j7oM1HJTAkVm2D\nXpZr+K03RbB9S0NDfMno19Vv94U6kgbq7AqZU3w7zZOBFgpxPryEAI+hJJhX\n2UDB4abpWamhAIS2dcCbqw+QdR3p06j3qQ6gnA9zyNkplSmDkUCj1N7nM9G6\nsO7b1dShd/dXDtYV78xGQJYBQRBttLAmzpbcEyCnXaTrGjIgwbmlWHPri3br\nr6CjspdYt9cXKDlwicX4ZcgyYySpgowVjMybNdcBnL679hzWoULZFEI+12Es\nasZrjU87dSVfsHWRP62dQsQH6FvhN5uFW6/+jPQzxOhLJfrIxZt7mXbhppDi\nSfolff5sAZnlA91pQzZLcnNmCAf7HJHv6OSyp71kPDFFlOc5VkRxMHINXvI3\n2IF1WbHs6QZQDroDnKlUaakdPj1rKK9FpvbcJSnC61lHPS+dGh1F+4MoeJAj\nKAAQ\r\n=3skW\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDuV17DD82NaXm4ym3qpQlkec0HJfss3op2/7Fn4Qk9pAiEAxUKj0uL1T9Y8SzQQc7uPcnnsVtWe9iSKa6kaHMwNQQY="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.20_1627631914654_0.9283119130360953"},"_hasShrinkwrap":false},"0.0.34-next.21":{"name":"stagnant","version":"0.0.34-next.21","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"ef6977fae10458ee48c96d6510baee479f815a5a","_id":"stagnant@0.0.34-next.21","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-RAhDYV90PiOzOVffVehDOxeyWl7eFBj2QpOUy4nWZXD/3j4dgHnumpWlJMXN6jCRq0KJ7dLNWx00kLQs2r5Tiw==","shasum":"3edba8428564b97bb54219163bace5d945ce5c36","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.21.tgz","fileCount":14,"unpackedSize":163952,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhA7JyCRA9TVsSAnZWagAAmKMP/RVJl8s+gT2N5soSveV3\n4DgxePKH9VUYHFl1pgwWP/CdQKgUsLCpVjaoh7ISEccJ4u5Izk+Z766EE32u\n3XVCxmWtqOEuOxUdQTh2Xhl/fCzVO91h/0sfSzJul7wwPGCqlpErcLokS/Cz\nFNc0Kubqmq4Q0Z3cVP0pfl+abz4x5K/mpDn5TqxQyXj4/NzyQOPKynvwSzOM\nFkGblokAPpgwrjfFUNINw87gt/um8+nrJTi8hhYRaU9IGH12J8cudn0DFVhZ\nM7yCkjbnQieBSqDEc1ZL8GjkP2MF+2M1CHJ7/E5r36UHQVTBQD8XBcooZyRy\n916JHRco5A9y6w7lHfMlLDXlkS9miSjhV4r1cugeG7dh0EUE+YFRAycnMrI1\n9TJbIt9yl/bIU+y+qwqa2s/8tiUXfzsvV0G8Tg576SFIyXDtQhzDwjoVNiOG\n3WzqMAaNwzmgMNoSVRM7q5VNbmRONo0v7oekgHyZmtC7QT+/OGVeLEq+OvPF\nAuVydWh8G8MuE0wRDsPKlcVmstUwcgEGaYWr27mU3X4DZ2F7wvjb6qcJDpy2\nNLvO9G3YPnM4zsoOB7+dnkdVclPaulPEA2fhDtl4vEvVNvm8vWUoBIm4DB+o\nqpvY72TT1tUYfd4awxtt3ml5W2GhqFEWtcdrE2g45CUUUVnSWzew3AYVFEyS\nuOHw\r\n=zzp4\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHmd4Tfg0KqeFIUMlek9Jifd6JjU+tOo6DSmJKNUB/DWAiAN6Wkcu2Uy4Mnvnx1xvG7YNg8UaghctezEO2yVzQwNmA=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.21_1627632242738_0.7687024340365642"},"_hasShrinkwrap":false},"0.0.34-next.22":{"name":"stagnant","version":"0.0.34-next.22","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"088e89d516aa370ed555ee1bb98d620abc09576c","_id":"stagnant@0.0.34-next.22","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-zfqbZZCM05VkEM9soSDmyTrSIL5dcnBvJkeWP/INaoogH9t2FiL4dpazk5tZVESSZKLidpV9UPrUB4nDg5bvww==","shasum":"87a27ed17d154ebb5f4c83fb27dfa5d25ad5f93a","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.22.tgz","fileCount":14,"unpackedSize":164779,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhA7v4CRA9TVsSAnZWagAA8Q4P/A3hWEf9amb+t1nBjt4r\nZZPSe+zhUEkwj94CApzlY6EEVQ1p0L07tL9jFT9+GwXwpxjjq65bVKtxZ3zP\nvSQK/Z4WmZ09MoS1qy5D0CEvV57iA/ht60sGpUs35XeabsGv0VJbtv3DvES8\ns83swRT420ugvqvKdPcIdqTSp83Oowy2SVlmvHjXLV0tj96jtjH3V++r/szK\njvzVBhVXXPwphwzGbAgkupF3skXml+R72IGp+9P4TuGuaSVSYB69lbYg+fqv\ncI2g7KZnbGCrOgnB+p6oZXLEhdVKxWvHz8X8NPV5xipD/lknefXtJMIm8XkW\nT/mDHsUa0/yuQZOxiUKAMVDq3FE5w6zf6cUa11J8MLo2DqH2nCGrzoCBAapu\nFw/C90+rL9z/otTyN2bbATlpawInFORbF7LmhdDXri0keCX2NwMglYFMazfx\nplHHpUJJROLyNnJT91Wpi2taZviNepJElVXc6Iywu7V4J2SK9uN/3s81JfGb\nz5H69zVdlwtUkrdoJ4JpZ2QLg4+D1u/vckFH0WZ7Wn2Mpi8DeMDCRnjqnuQ6\nHbPUOgPCOO/7baG/Lp4npq+rdQvKwR6emQdwfv5UaxTDdwknNuXBlC2QBdle\nu6gGrAQcPb2+pw6+C6SwwjogaL0XZqJuIGAYWYAIsHFeyXRwtjhZcNC0A544\nmCYi\r\n=A0Yr\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDlewoODKsQQppX8jLIfxcfxeNHV/2idiEFpnGtlHXLrgIhANFLZ0usaBNAJ7CedDEBGJtFJEVXUHaII5zG9N5Dx4SA"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.22_1627634679689_0.5583428944225133"},"_hasShrinkwrap":false},"0.0.34-next.23":{"name":"stagnant","version":"0.0.34-next.23","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"5b9190161ba6110ab5a99cf75e65be8bd445d60f","_id":"stagnant@0.0.34-next.23","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-3Cl8YdlGT6Ff0oBkfaQY33Mc/0Zk1gSB1VZTsANa3Ukykkxv0LFKewwz851YykOofjv0KR5o47ReAqk3mINDmQ==","shasum":"272045f5236be9cf044705ebb2063e91201e29ce","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.23.tgz","fileCount":14,"unpackedSize":164450,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBAQxCRA9TVsSAnZWagAAdDMP/jqbK3LFHhKIFdEjFWXq\n0O5+xGInF5VAXG6vh4wcf97o2f+M3+3cmve68+U7I36VqqsEHBVs61KSoOOG\ncCDdpJQD8EVSOZqwGcTKz+XzvP5TF+CsbMrqEaoY7AZPOJLAKihO5WVmsipZ\nZYd9ng9pTNg9I9pfyeUTg0SxBcExcoys+V1dDUWFb3ffP3X58AHhUCKD6+7M\neWtMHoe3zFpipVcjxdYadHHLGE+fBIEzvOFYExqaqNJRJRlM9LPRbHt70guq\nao8xrVpGX64AB1s0HaWUDgHD+BgogvGfh2TlRm94DqLH077bkfdsyAhFI3bT\n6rtID6ax8fm81pLz3LVbqIKFuA/3LePu3ehnt+0Ss+ljcnBGS2FOgac7o95t\ns9eKwmO6dNQb9hzhUb/hxeQcE5r/HxpnCYIuKNwdnBp5eVXGdwtbLkGzfQoy\nZUhv21NIhvao5EPpBV545rjPqo5UfDZB5IHgRVM32nPXUQQqwzvmL6M+jCM6\nY9oYg1lC27yC+ud491BtrjUyQwSYEWIjpYlHItzLhKR3nrXYLo/GpT0VS+xk\nniVHwMsq9auGozxilcGQM5Tkvvd4MuaOU7SGm4vSRrc95mzxo+aZcs1e9PQq\n/Y6rRytpxeL4ncZc4j4MmwPMLRwNCuTUypzCrY5Q6D26fUYQ4dhspUhNOcL9\nlI22\r\n=AeKt\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCUyc0Sf0BNKHAQk83ZJRcK5sG5dmUcRLFMfN6VIjHnyQIhAMECTQuE26zhx4+kCk7FGxAlptGpvkIlf5bOUFXtlwIz"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.23_1627653169292_0.578841614389171"},"_hasShrinkwrap":false},"0.0.34-next.24":{"name":"stagnant","version":"0.0.34-next.24","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"d8011023f8075ed7d72cb5b74894512fe43ea4a3","_id":"stagnant@0.0.34-next.24","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-2f0EFX45vLXhHAMm3H7Ivs+KfiOcSiF8jcs2jIWbfP7VxO4TkJnAy7yPIvkvriZUTnREgvrrB0hDt/P8GiLF2Q==","shasum":"544cb48d904a93320a63b964a17ad6cc505a65b9","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.24.tgz","fileCount":14,"unpackedSize":164894,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBAddCRA9TVsSAnZWagAA6w8P/iY8BHCt6vJBBYWAd9/3\nsy1spF7Bp33W4lbtwr7ovEJSZOc589fwQ499trTszE7mZ2ELveJtA7M7MI4e\nvXC3FFldm4687qvBr+RHzktaVHAwOJzmpsf+sYcbFJ9nTf7f4ecRHg4wTlGd\nUIqoujagGPyYGGWNj6lJ1eJgyAHrc5wEZG15mnMhYEDqF/ijNQlhKjSFHhb/\ngA7nciNNSDdvemdKzPLCYnDuQUXAEhXX2b3IIEzjiqXRFc+uuz8K4SoM2e4/\nL2eKPax5KQTB6QWq7Mnn4sxjBislKNujv7AijWEyfZa/o+nEE4jOW/ImByeG\nu98UH1wZzQHdexYIPQGlCBk9J7A0MjvDr0c4xwO92SG1+gATnm/xTal8DKlG\naN2FFdULYBh0gRSdgrJ+7D6EVxieI6rDYYzC5jV5/jagta44bhJBDNea6HV6\ncSL45gzICoIdpZFnk0C370B9G1Ayuvkoh//u/DINU1xQs8ql9s32QPRCoWje\nwPxJylRTnRIISU0XQPNJmxxRgVn9321fHHD2sXTixGugnMTWhasEHhVCbgwY\nlj2AsFkT2I+CyQ4g26BAjYAi61BQqHqGj1TIUzfogcjZgY0bPXnpLjlW/HE4\nSRsMNxM0/+P/+oiqVUjLCS0O1bP7N/1kmjUwKxa3NEc329XdAXQdgsg0iLvv\nwZ22\r\n=/Gp1\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD4EHRKR+OLT5f4wNRJdIEhDfauHuHUS3fTo3arjt8TmwIhAJtoenEv6gGzAdTDTnZVCxrSCveqZCywgzVp5Q+djsmT"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.24_1627653981429_0.41542825483847357"},"_hasShrinkwrap":false},"0.0.34-next.25":{"name":"stagnant","version":"0.0.34-next.25","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"da46ed229b1bb9f029df12c87c807084a1f6e905","_id":"stagnant@0.0.34-next.25","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-WEiYfRW8Cc01TKiMg97FKlM9v6AAV1Bys3b8gmjBizs90TUmbewu0vOKAGfnms6NIeR9NYdfJpODwa9WjrzeDQ==","shasum":"f0575ddccebde2d1fafc164b52b8417a8ca516a7","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.25.tgz","fileCount":14,"unpackedSize":165141,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBCBUCRA9TVsSAnZWagAAxNEP/2VGATL+qUfNjNxHFXIk\nwCJC0blGOlg5lLUJXi4T+ay3FP3uWydwetjnZOtQoWPDS5AtIbTdUpnccwk4\nmEqchwtpPeYXjzZDdLqigjX/cEgiQr9P0LkTrKpdW8gzO4OpuloHxeAq4jJO\nWPhcMJ//SRClcxYInlSMWKQWJp0PEI+YzUFg6ZUqudMDai6dHRtImerkRPcV\n9EFmOKwiqIg99FoRKiZH+/LDTThThBGvpf/1IACkbvHl4TKYpFNWvuhZLX0O\ndAn+WPf3S/R+ZI3CHHMGf4REWUsPgCreEeJZ7d6RdPyXKecVufxLmDsyNp6I\n4MI5kKpIh3QyS3rMLmiokl5LtDu8DN9aA9kZk10FOAmY+WNHie6LJpZv6+Dy\nKSyRtnCETAZxrK/pWPP+UN47atBs/dY+2paQ1ElC4bXhaS7gCGQEhJeXixxI\nHt/F7RQV/2tv6WCjXyENtTZ6TUBmcA0/HkQx3A94F8NOYBeZy2LXIze9vPWU\nZOugjHzH71cIKPaSX2T73la4axPznzTV/w+XMa7vxESezbBgEjBs1EYwTfYW\nlpyVndYmkOo/Sl30OQUA5yR2mdmDEymxOfJc4ccP2WREOLzd4VugMivDTTSE\nCwTDKb950r/JCq65Cj5CM0rePyTPGHhIHSJackKwVLsUrWHel65wBzKGOaaK\nfcOq\r\n=jGsO\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBjn0GaSeyVsdQbOMUUnWNkyKELUP2s4PNSPUWiuwsmOAiEA+7l6WQJWvyp1J/otbA7W370agedXu7sJv+M2LedQuW4="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.25_1627660372579_0.20328612129780277"},"_hasShrinkwrap":false},"0.0.34-next.26":{"name":"stagnant","version":"0.0.34-next.26","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"37f4c500a3b6e426b0d2a93d76285d695ff2ece1","_id":"stagnant@0.0.34-next.26","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-CUTvX+WtzV0sRiKnw4cK6DmmZ/UULWjG0RXo5oZxi7sO0/Q89zTCwVLS8WvPoDyup6GJPSAyfvyc2S3SEThn+g==","shasum":"e5b161c16c7f3bb4382be9e5e26967ce5af63ee6","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.26.tgz","fileCount":14,"unpackedSize":166969,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBCTICRA9TVsSAnZWagAAXaEP/1E86wPG2TZEpufxJXKT\nhEz0HU3smwVjYoA5TmiupSvVTBI3Url24U90XlRhXc5+KVWOB6KqqK0+ZxHs\nAm6IgpYnVymSDs9qB10CFbgFW8OVrM9i+QlcyPDkSQSj5AVfsgpDXXb+B9zc\nw/pYol+C104n+zFPmGtqpo7P6sBMzrLxMNG6fytHN5tZ+w25swezzOf8sEVf\n4ple1NUToKcJmtWSD/GzNX31//IwjlD0bWQhYUsmMPpWm3H65JCw5AwY8WOl\n6WTZNNcZE2r6AEtNYEKQhXE7mAzg5LpZR8sQHdkTUwFm6s0V+gdvjdvDRiAe\nbu9JM647zGnjcGEFogw8pC5wRZp5HoH9ERQnwthkm/S7Q4KzldvdYHyAJlnl\ni5f1IQPZL6YDaZJmtT4xxJTfB/cktVL3UqBAviCUslvw+q57QBsHdf/7dWyj\nd4TbKS9ygQEPCv92S5zsEI5xFsGgAqRwqvA8o6RXgxlo/zvanbdFq+1+Hx06\ndRL8V2Zg+5bak54/aj2pekglpSIjpse9ZtU5n6KCbiUVH9lj6f8rAiQtQ7SV\n6fPzJvhNefY2tMj1mGzkpmvOKH00urlwVtV9V1TcHIHKuryakcPZSY3g5m3L\n/pl1EMBPQn48gsHrIOUzcvR8km8lESEp1Q7BrP04F9aARQ/wg3cUfMiL7BVE\nMBOg\r\n=jTWq\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIH1CrxshBou35IbbnM+WiSZF/bfVGJiGpugZGniSh0h2AiAHwUsrjXLheObD8BumoZwx/S82lvoxmR5gs98SkBnKbg=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.26_1627661511972_0.3716553730474794"},"_hasShrinkwrap":false},"0.0.34-next.27":{"name":"stagnant","version":"0.0.34-next.27","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"859fc77aedab39975c3b16ef02ec3cb477ce296d","_id":"stagnant@0.0.34-next.27","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-i5m+SssSOmQ6fnNY6XUDoaj/YvnDBNe4HJu4fE/BtLOMk68OnwqApxKCDfcxPrhPKyRMhOS2EMb8Z3dMxmlUeA==","shasum":"134f44360a484b5cb035c561763115f541e96842","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.27.tgz","fileCount":14,"unpackedSize":167386,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBCpVCRA9TVsSAnZWagAA+V4P/RVpbbx++5yXGWySzrt6\nHQ1zJnCo1vij3CDBfsnORjY9eYSKlN+AWq7F0BGtMWqd19wTegwnN0kirySy\noxLsPIvj3pMMjhPNX3MqWc+dwIt+OQ7mswvFKcl8hp+Wy+Y/NE4NKy6OMQev\nEGiJBYfW79jqR0xxtgZO4Gq27+IWdpki/21Tc4tXvkkRBLCNPuKh0TzHi97+\nUmdgl5xhHcgFcckeFzjI9HOXPcyReKoYtzKuIXUxlsSyJdWz1eLOV3pp5pc1\nLlOkBcZJop/C07rYTXL8fO5D5eYecLwU8QzwHSiQmmh4PJOF5ZNiMFYhZqeA\nzrbxNuFt9zMFYgPYhJy58VY+4ountHvoNd8f+Ul4yh9yAV9jcISbwLV0CiF+\nliPFQ6804arA0AusFrzEiST70aP0kdX3MikUaaqp6i85bpzLYWYg8Kn2D5ZW\nEZF6/2u+mUydmWnbDz1fBfQ2S7n6wiuKOopJqoVqxSNj7OuAfbR28DHvDu1T\nZtr8G92aQLsjM7Zt/csk4JotgU+1t79UJGMl0T9OaPM3T+c5Nudx8CcELzwg\ndt6ZYSOjC76J+exqK2oMGt7TrDnescNQtpXQz7gax8e/h1Hcb79q7qlb6Tbd\niGaiTtgXstWfJIEQW3o7LRV4ubaacTQPRkYb2LilLtC4QzsTklSZm3cn5Zsn\nSmoz\r\n=jsAp\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDhQmvRuTovYf56mpOFXY3xrCVkRxZY1doPFZstJVSccgIhAK0/cggwRDKY8pHGNturM+pUQ1YUHV3fW0C84yZPlhYd"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.27_1627662933703_0.033386364730430884"},"_hasShrinkwrap":false},"0.0.34-next.28":{"name":"stagnant","version":"0.0.34-next.28","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"20dd5eb8e0b20977576ce5d9525238dbd8494ba0","_id":"stagnant@0.0.34-next.28","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-a3JLePWakXJ4IYG9G9dSj3PtOonJIvq9g3iC6P7zlinEL2EzvTZHMwD3dXpEuG5rgAnmAN/0bEUg4RfFdScQwg==","shasum":"8535857b792a11b19152339762f46aa032b26267","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.28.tgz","fileCount":14,"unpackedSize":168164,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBCw8CRA9TVsSAnZWagAAW5AP/jIkPHdcLIct6c2cNP9W\nFJOI1HV4J9MvzYX2MBte9svU0NpW/ArfQUtp6zlWoEWEo4m9pWXptuBhZ5dU\nyJFf3Y0m9dA+EzugzCrx+yjg0+NmIuAQTwJyiqatVYrZwicPE+5sc78L6Fnj\n3KhgaKaGjff0ThrgfVsOuC7wdGJ+/J37SqP0S5aBCmjPOiGzkIHAhGmIu44H\npUZ2uGr+ALyVeHTDgST20r6kse/kG0YKNOLkbxEgCPOgpN/VJdF2ecJqNxsP\nJnSN4Ri2dqSmixrYwbT06lPOoIALgWidRtPjEu95Ovll+kTNHLi3fqbLUhbV\n8xGhBeMOSm6id7bl3/uKYcDDmVW6XtpYizzCTBmLcLeIIBmoD4PVl22dGzRR\n0pARGf5jVMWr1hQp7dWnfvB0wg/txcHaLLS7yuzNpraku6hYZZMLn3mlh5E/\nLbqe07WPCGt4zL6X09fKgdKzM1kNOdUlKHHKhbkOnAUjVzm5nNcMnKGi/sqr\n9qj0fkj8aogeTMCgNVlWS086Ongqlq1z1rZDfVkYaVkTHFTazIeAvNrjDycN\nwF32vS08zBF11zaPw4/7GammF3g0T/dqOeee7UpAJMAMJ50KA0YXzqxM+2Dy\nI/bibGQuabs4VNbfI/pEn54eNBRXKQ95PIv5D9fPP7x7R+Y196rydLo5aFHV\ndNoS\r\n=dZEa\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHrTTNQEAbIOJ7g7BWG8KugghYh+SqBsJiVY73ysnRZWAiBQS9O61tTGhbONmxbyAIdVA+bgPB+iN2o7ezSSJkOctQ=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.28_1627663420407_0.9008234670199589"},"_hasShrinkwrap":false},"0.0.34-next.29":{"name":"stagnant","version":"0.0.34-next.29","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepublish":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"0d1602dc0aa75b61bdc2227325816b4377842805","_id":"stagnant@0.0.34-next.29","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-bqfdjsvBULg6A0wwvR9U6uq0thisr7ihXmodJH3UP0gXBoGj1WC3JMK94a3sMI5dSvnpafMef6t1X9mwYndpsg==","shasum":"67968451c08d8e6d99c817669337db3efcef4936","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.29.tgz","fileCount":14,"unpackedSize":169825,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBLK6CRA9TVsSAnZWagAAevoP/ikxoGZ+2qVhJUHKErZ9\nUxcOFBXd8LLd3KTc64GiCG9q8YV2xlTZ5ZB5gYJ1EtleHRRcoYzrbuHhXQNU\nWOiVhk+A0/r078S4CJXEa2T3LDUHLRShJoJQYJmoEYgwTuN/QyVcVvPlubaS\nD83rCQ3U6HneeQoTTAfCmFL5zSaCud8sNQriuGZoFN+Zcy8Iq5YjzohV1Jo9\nmt07qqh1OKjOTBO8m1XX59Ygvk1lbNK5cW3xdtrODY4YzIdavUGJNlrS6iPc\nSBIZzpA3EEr1Jo/5gciVuFn6+FXGtHE4v8gLAktDc/dt3xxlRM8p8D0CCW8H\n+5leCEU08QCTYxqSr2xEpAsVtvnGoLdweCDbUWi29fztCQywR9XqwZhwIvgc\n3LaplGuSJdQ+1NHBknLFlJi1TwpzAThLDcN0IVXRq1upUd0WfQ0ZpqcWgRaL\nDymVTYbE9SbkRM7adXYDplDCS/8klxrftsIg2IrcwR928bn1AwgxV/un6bar\nk1QiW0N/yvU3ffNmzUbHtolFeAEHbhm2pMjz1QTi8a5qmuMaf/34DP8R2weF\nepGnNQyaXM6fsXnEgKOunJZY+QgJrFkjBstHB3Eaikmn72ZLcQ/T23Hz8mLZ\nXPBySNdWPKXCfNuYSqeFSes3phQzkvdfns3h2lx+mfePV+NXT1VM3lt5Adtr\ng+ws\r\n=QKQw\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCzOQmlf/OOr+hrb9sE9X3s/VT7Hktd33q0Rg1KHlYF2AIgPDrfKw8zdY5MdDgHeMywIStbDKWIEgyGD4P2wbHc6bs="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.29_1627697850595_0.747913948035309"},"_hasShrinkwrap":false},"0.0.34-next.30":{"name":"stagnant","version":"0.0.34-next.30","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n","readmeFilename":"readme.md","gitHead":"6ef34f3e96777e733ed69af80ff395f0cbd4554c","_id":"stagnant@0.0.34-next.30","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-HFR0fF9lOvh383UTH/CjYEFg/Rt+kt312E1BU1TvgtHFnOQmkb2HTeaSODd2Pssm71sFF3kBbwqW7E+BBqiw2A==","shasum":"bb31b89d324741c36ab73518ee61c78c98dece7d","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.30.tgz","fileCount":14,"unpackedSize":169822,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBLLqCRA9TVsSAnZWagAAQQEP+gPQ9/fLkZSNLc6JZnAP\ntVAFXQFA8A+XhSx2smyITuBYC9U03q+NCuOU89VYoDRWBSx+057P8QOx7Nu5\n3f2ueUluSIeiopEYzRYF1a4+VTvkVwl0cYrLXc+7aBcUkXMqbCpk7YoKk8gQ\nGDH4wCYT9hZhcMvjsFCDVsvyQyAs3JqYWvhBH6Vaev3URQJpa88e01UHHtN4\n7le4orgCal6rqrIL7iCrmnYR7zMlBNtvSMGis2j63bjQxbynliivl6zKHY8M\nWOags1WrC5vALSp02S3KSKrLL92loAkNqpwt+BgPbqnX+J182n1h0ffWIccf\ngaUbvBaH0EzP9yaV0TRLe2FT1dCAnYRTVrrR7ap9rXSGTwmLNwIjMBvrTfwS\n4oVx6nahuG5guPH5sWbudto/tlIVqqZVK+9vUmcw6Xg2Zzi6m0tWlUc5Bq9U\nuTpdJSXsKPXI+2LWGzTZFerUTfo6DrDFsTyq9Oof1mfKT0ythddd4CuoVWN6\ntthahh+lKyqg6+NQGLW1+ztDoW7EboVBwbikGrsyoUvzxfXnIW0YwrRqY4JU\nqk85QHZK6j7FCsj/61eZU5F1JnmGrNs6fqT3N7ciaD8dEGvB1pWrYvJ6piPu\n1QZBaNxOnHi7vkn9TtHNdyL3Sm2pAPY9KNC6HLO9+a/URe9zCWOweakQd25Q\nU5oQ\r\n=Nerv\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCI8XHEI0xK9cZN+EvFS5NnNU9cs/h+mF+k1tbLVofmhgIhANVai1TUuw/6U8U9uzcDhE8ucLVdQSDnSI0uJWRis1JS"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.30_1627697898545_0.2767033337965161"},"_hasShrinkwrap":false},"0.0.34-next.31":{"name":"stagnant","version":"0.0.34-next.31","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"61a30e4b78ebf6a80e0edb360e1d410899369611","_id":"stagnant@0.0.34-next.31","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-EBYYjFl8PH6bK6peIdAoHMVbPDQ69ByAhvxNdEKU+lJZ5cASUpusPwnnxXGmxWtzn+kdxSRQwZCg9qUJqyu77w==","shasum":"dd98552b8bfb8f3b10c0b64f0b69b2bc50d09b50","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.31.tgz","fileCount":14,"unpackedSize":161989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBLoxCRA9TVsSAnZWagAALz4QAJa2lJddc/Uf8/03v1vd\n9ybhoLWXwwnSR7GXqvmYmJA34BlLAg+l61gWGI8TOrf3dN/Fsyp5buK1ocxv\n83Oi2m2I+QbNm2lbgb4rdzhJDAS+upv/9drrYx7LWzJpQuZGvsVSX3lwcY3S\nq0QJBMIg0rjDXiD9w+BsWCbsXflINT9S0a1org3UlOGkB6yb8AKLICKhcqdJ\n54G4TMy3gTx1BdaxRejQWbDDNu9rXs2R0VliM4w+Hdygv90wfVSKb2rbeMRU\nIzE07BQti03RSLXIOybzqObmg6iWMafWcWj4cB+PUng2iEm7E+SKjFefnKca\ny21DIgKuvHRcRjM0vbmNDn2hx/6JydvKYfsNjkhfsxxHChJG7g9xboJaqidy\n7P6p6a3kqdwvVJwE2ywluoICQpFpvK1wMC2DsL6sHu8RGfqDebOn/YTzyQRr\nXHKKM/0WFjQqQ4pNTW3BEP7kawRwJG2UFMPAvCzIQYToSpSHBHWSQJ8i2J5D\nB3ugRuj+cfzQiNUIn3x7k6WWv+UWrAmNuUrZXXSO990/EXAYgO7Rhy/PLG8v\nFsz4zmyoKxe2mLUT1KN+hs+qxCKSQZRNR3/9MmcZkMFAPGDyy9Ru4Eicr0uh\nINCZ+z/wa22uYY4Pp/1LGFyiq+eex1sofeHUOLvFQBamTv7bYO7bd+ULLtQ6\nwGl2\r\n=lUSg\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDN9n0kh2UOCSqV5wcZDRulO59zUd+4RwHZtHDjdcN7HwIgNvNjHbV592P0DFnxgpfzFkGEFl5cut+VjAG7PFGdsEI="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.31_1627699761695_0.5780006602405057"},"_hasShrinkwrap":false},"0.0.34-next.32":{"name":"stagnant","version":"0.0.34-next.32","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"f3e8370571ce8d7df665ee545b32ffde6e256d50","_id":"stagnant@0.0.34-next.32","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-JQ/ECXv5qnb61BJIEbCOCIpEO775+Ky9JIqmCiKdmRV1NwaLkOO0RaDQUmEsQgMXekeQwvx03/ukws5AQdwqdQ==","shasum":"c0484b6b08e020f2b71ae99010506cf7f9f0e2a3","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.32.tgz","fileCount":14,"unpackedSize":162275,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBLuRCRA9TVsSAnZWagAA4IgQAKLUxEBkDnXFA9dJEIEg\nlEjKPsh8d4lciOdXjpaCX/A1qiqJihmc6161EZF8F7+VuTeYK3gE3JqQeV6e\nObeqqDuL3HRTc1EJdfJk1b+iYL1eq8njBZGaMLxR9yKWhiUoG00rbW91UvxS\nc0tNArXzuRf53/TAm3Scsv49PCNsyiFot+v2g4krvgklHx8YWEzGZ57UETQY\n4bT0B3qFXrexXW6XxbTiYF7JbyCBkg3bCejEa1O3NLgbpd2dS+isTEMweckK\nLVf7ifmIDyNZdWsy4qPE401bjEy0il9/cGu2RMoD996cNh3ZurrETmrsURBb\nFNF6X2fqyfdo4cjNSeiGFXluvB4hE7+Fi1gYZE1ju5sChYBl+Z451E5W+Syj\nCK4pZlvyixCnXKt1Q0iYVjM02tc75WJjbKEq8MNIw38a4AcePxa439XisG4x\nTTloi2ox4gCnyXsYoHW+FHXLyCKpZD3bmjiHOK5pMU8qTbYBwwbPbj0ho/Cs\ngfPtxEqDy4nq3Z5Iklxs4vakLhAHlQkFStTzDGw4kLGOQ9aFH8STGRDKL7LI\nQ1L1GvkD6Fz4TY51r/A+MOmRLMXHXHlnIYTzxrnaqxsRXGeUEXniXj8NFx+v\n/gb5mhd9MJlItfKsGnD6QJGC4s5EN6pwRFRwvHQDtWmANFXi8tG1QhNd9Vlr\ny2Em\r\n=XEEM\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDomi7WG/JSCCqFTP/qWQCu/8P+3Q4blxgtfAUUXD30oAiAxB9AKdy6FRJxtG8MHZrjqEe+6MpHozFrZkdWs+bcIgA=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.32_1627700112873_0.7356936652619213"},"_hasShrinkwrap":false},"0.0.34-next.33":{"name":"stagnant","version":"0.0.34-next.33","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"e55cee8e4ec310799f17fd4e9326f386838865cc","_id":"stagnant@0.0.34-next.33","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-5MfnIdJa2Pcify8tyCn9WHU1Deg9OZzu7RQHKRZqzLMx1xfsIQf16wWPVs9BKId73Hg2xoVvW0BDbkGDCbSWJA==","shasum":"546db06b03db50f8a4b87c52e40482948813a798","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.33.tgz","fileCount":14,"unpackedSize":153315,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBeN2CRA9TVsSAnZWagAA4swP/iTCyfcteGdjE0Wbn/K/\nvvWD5J1bMuMcOkdY3de0gYaAmBPA+fmXxm5cdUfm4LUU1MBP77Dc20WmaZct\ng472gEPqwEBqYBA6oXwUgmif5fzVabTI/EQmflWzlKv242zKbqc97KNJkn3Q\nBNqHw+IL0v+mSZKRG8Uch07BBsecAJTSPSDUWf4+W7OtcrWPSPlmI0d/kDCz\nfD1mEa6QmzpT1xevq/6xcIRj+MivQv/Erao8A38CAOpg0UNHavXNIDJBwcuN\nxKKSx6GGxcKv/EiwG5U6/EIjkyrlGow1XXiHktV0COLg3yaUHyKvGjRCqLEb\n/VTxAnIlLlZ5p/8m2DTtjsDSaM26MaoJNuz6IqIYw7cUCG4z/fY8PmGgdp5v\n+ErGkI+3clAZnFTTiRLrRGvrKS2sYhQyg36mQRdDNoFRJw+h6Yk8gFJ6ZfAy\n0GU2SFupQXCAGLJRM9Ao+mWPyvRlhm8o4eLdyGk+PKZ3rd2N8qsJfc7iA/KV\nyyN0+gPT9EXj8xv7mXGlz3T3tBY37fcQkSXJXds4F9jcYE5ofksZSEMFBvSL\n9nfaJRaOmRV+39TXEpmd11F7nco+FmVXn+6atDYJQexzK982Lk3DDn4wtCkA\nt5u+htUIXHOhj7ds7rB/Bp0KHiPgULrAiwMks2F5dfoApxXoco4Iz6eAG69m\nGvqK\r\n=KGw0\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDX54eeDj7KSxgEUSx6/ulDgxZWYcEwUAaCSvbVSnbrHQIhALmzcaE22YdlCAAWLHCiyFpVXft+bSY2o2FrAWorH7x1"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.33_1627775862219_0.7050856300489288"},"_hasShrinkwrap":false},"0.0.34-next.34":{"name":"stagnant","version":"0.0.34-next.34","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = stagnant.ensure( trace? )`\n\nMuch like stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `stagnant.call( trace, () => ...)` or `stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"f7e493cac04a2f8ceb00643fe52664587841f8db","_id":"stagnant@0.0.34-next.34","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-wlQdlZMqXjHaU7XgJjmnnZAlulgYQGkGK4LwuYkGcwGdcqAfhiyBBOnCTdbhK2P8UWUb/wbVwKyK3/BU2lh3sQ==","shasum":"fdc4a976cd45bc77bb9eb086192d8d4a2495afa9","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.34.tgz","fileCount":14,"unpackedSize":156044,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBfecCRA9TVsSAnZWagAA54UP+QBCtSIM5C1YjZ5yIS6z\nGwyTB84yxVMj2/6elu43sqAxSrR+C2Gg1XRYatAUXfd55bSVfsnadJQoe7Dj\nH5XdxYitfG1Buf8har7XuL5TvZix11LllFUf1Eorc4sR+WR7pObS4O8dX+5S\n3AV/Lae/QTyecrDNYad21+uvoD2N+8dHZsQBBtbuVzvq7L8z4D4FqsdNi8nO\nkQzENWsAWtUenil9wIPgTgY7fRpmZV2hdE1VLckLk1hH85kr1q+dEBzZ0H6w\n5JgDdMpJFXfpt1h2kMWbiuRAfDHrFqqhxBUzwLp2tXyzBQ06LMJCSUPwE01P\nDaBsBN9XAgnOcTaXEJwoCxhUViWinN8/1ELKo9NSjGT46Ilg2qN2DfMP4l1J\nHW1JmDGDo5FOobzWyngVCGt+9nh4dnpnuV9pQqRxuFMiuDGPQzvs5uLh3wTf\nPB1ebJzsgnKds79OBRKZy3Q615J+I8QXwROrMo0WuyGbF8tM79LSq9HXE55n\nAErtPEaZzX4G3wK4tk7sqgOxMRdyqxRx5yGLzsMuoKEBhJzno9NhDsfu47ze\nxsaBIj04u608wmhsW5B3NlYQ5l0CFEXWALd2VnOdn3Eko00ZTMZDbDgT8INi\nVIO5E9TxjlhbIAAmrlLtlfubeAjt08SWAC2hiKRd47nzp5hiQNYaLT7olkBg\nCk/Q\r\n=upp2\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEuq1zecDZKb+/N+cTtUS6Q664OyVBACCVtvpBtXgQduAiBqlqOxnR69bW2P00BBw/WEipp4Ak6+q5APegLQj/wVfw=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.34_1627781020755_0.23475625772158137"},"_hasShrinkwrap":false},"0.0.34-next.35":{"name":"stagnant","version":"0.0.34-next.35","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.js`](./honeycomb.js).  Check out the [usage script](./honeycomb-usage.js) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"792b3b5c31787fe2bdeed502f1e9f94a9fa9b1c7","_id":"stagnant@0.0.34-next.35","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-qRvpDbPvEXeJXFbYqXa0/2QTKX9Esr60OxCmv9mMN7iUJ/vZrQv6qTOK4XNPPORAZH7BfdESXN2gU4gfOKu9Cg==","shasum":"b759f3b2275ae77c164f7da8fe4306d80ccb8001","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.35.tgz","fileCount":14,"unpackedSize":156044,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBfjGCRA9TVsSAnZWagAATv8QAJ//F8mNoYekWFCqAoJ0\nwpBnXs15MzBM+LH1E3ef1zlVMxqz3mbJhpWFiA2qt/XH1TRfq/58eusHUmlm\n/F31RMPfO5apfGJlSATamWXHIN27xv6BEMdv11VEnodQHmZt4LXc7E5V2OhR\ny0aT23/yrJtw2I2+DP925bQph9zwmOB4vQKG1I1y5G9I3U8r3dUvszsgquLR\nyZWH5vBG5b/6EMXmyX7JfGzmHfv6X77mHJ6csXC6Zy6fiwwKVchWAd/Sid6y\n/AVtRjeR+fCdc3+a7N+Qfy+b7OB4mgsXL3YfcYuAg++5xzISUZkalIw+1uXx\naYAT6xiVwbSKxThzxu/AlW+gxscPT+/cS0fnEkhvDFmDXyscpRN0s0VvjGs+\n8JoWZevKZ3+zFzLuHLR4iEpRTCEaruTIaGFe8/Qpxw3ikAcV754W5d6y8KYW\nKtIoM6vYuB40bxVeynmzO0bHTV+iQw4cytI51VPOC/KUjncF5KEpXFgU9g+7\ndlIuz2Ffd1xIatLGftOyoIo0JsGZ2XR3KomMIK/NPU0/jetdNaWkGqIQPEjK\nxT1ScLjhUh2J5sl7xzgrhvPaoanqtYdRbVRwFhuoLIXgj1r46FuF7CbbMVjB\nXgcolN1FByqKiX09FHXoYA3keR6CRJEbDOVKj5DLLhBfsqODT3vpfRcaVFzq\n3RIO\r\n=CAPb\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDIwXDiskjYzwUixblU+F3CBg1qGbRFYrtWEXJlE7o6AgIhALDZPAGaNXG9HTY9tJwGeQby4lMZ19zGNtHCwLHaSRHz"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.35_1627781318641_0.6725737704614785"},"_hasShrinkwrap":false},"0.0.34-next.36":{"name":"stagnant","version":"0.0.34-next.36","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"45e3c43ccee355cd1924ad9d8c98d18943318585","_id":"stagnant@0.0.34-next.36","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-qT/uqTq/12afUYrjglN5tguPVUXXfhGt/KPEMP4rJq726Nz6DPuyoN3sCHDMxcIaVyJNbaRJVpq3cP7nVLmc0A==","shasum":"72a0939df169d9471bb1a768000f2994bf8f9068","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.36.tgz","fileCount":14,"unpackedSize":155328,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBgcOCRA9TVsSAnZWagAAJBIQAIbPOTdmdwtCzjRNRJOi\nXIgwWN1rwvNSUMm/Mz4lvmBGsrYK9/va99/GNd2PvcAzHdTd/uT/HB4gBp3D\noPPKDNg9Mtaz7hs7CEv2NmRuE9IlHy0zT5sSaqlrvySE9yRcnNc93HGA4dzA\nxM4h08V59w9lrrynhD97HokoEBKBSMHCkfyNDnqMpwbBBP9qeVaogFEXU2mV\nnEZa5IjheD/5s8KT8DMNsWP2PvM35rifXCYsbFtAaQZC4ion+g3R22MlvmL4\nNXQbeO2Yje6QWNrRJg17cF4SFpLlv1mldS9AZ955e5b+31YjA2XW0HxiXT9A\nHNNeWjLq4zzMo3N+bjKIRR8s8LbiPd1qpcpf17pt//etZIDvl7XoMOOfSip6\n5P4nO+VKS/TMx3d5fayVizAFce5eBkvZrigmwxoTD90a8c3tXH15ZVbaIyK6\n3P7osdlraT8VKIhEgC4fpMOf/Ruie+O8wmzl3Zm1TcrbQZJlVNvSg2VuOocr\nqSGy3eBU8HmysBC0CIHi0uWiVj1f1Nfdev3Y3mmegQs4swD/UrQqz1Qk2F5g\nx57ZbIiBGHzXFfYYdnCsI8bJfrcjAtU12agCnWeFZQPa+1LyfaXNd1XjpbJo\nMriu3yN5MA7pF0J89ktrK4428MEUP4U4GYOjHZUxQkXVjnKuqeeOOOOm04qS\nVSA6\r\n=a1QQ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDa33DyNlWTu4FHrP3cnhK+DiUGgGC4ILCtkbTdzQKaBgIhANWjDrYm6j0qOQPjWZGPYokX/nHpyGoyvt2hBFmYOfc5"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.36_1627784974280_0.6637719266793443"},"_hasShrinkwrap":false},"0.0.34-next.37":{"name":"stagnant","version":"0.0.34-next.37","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId`to your API via a header, then when initializing stagnant serverside call `let I = I.resume({ id: parentId, traceId })`. \n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"d31f55abaf48cb18b0a1b3cb5045ef379a2de433","_id":"stagnant@0.0.34-next.37","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-OPhiJrTpWeLJZzY/BQNLUEZujWqi5P3lKd7uICIWsPaZuHjs3XClQOS4swfXU5PoVfIj/N6mqoE33udXpQ4PFw==","shasum":"55a19a37b7abcd26feadf2fce3b154a55625357c","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.37.tgz","fileCount":14,"unpackedSize":155369,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBgkyCRA9TVsSAnZWagAAUs0P/3ndigpjb6OIAoasrKWz\nLGpf7atErgcNZlAJRXwfmUxgPhT8VPkm8AZcWsRKx7mhDO921bdB/W40M3Tw\n2wWaAwZe9NST4MJVAtmDZgWcOY2SOINYI6/N4XHSEqJiegaOsWhkn7oL0Ra8\nbTbQuEIEIpE9cvAqGgzhD5wMB9pWmI+vok4Akf4rOl7q/c4A2j5C2o+sinGW\nTMmeKfGbch32snlVLjMGi1UaWB3GglUNd9hPpYAdpgxKCHm1V5AJ0HvMg8ZT\nTPgtWTu5FPfVop68pLh5jSuqFg74O5wi0D5YgaN33/ZLkaP9LKhkxp4hm48/\nbv7nT5s4IhApMHJDggi8OSxIbDCouugiMl+2yt2FU5OaWob5PXcVCpRNFY9Y\nkMlbbYoMs2EYlQOo3KD9i/efv6uo2kG4iQOFNOoXlEuEAdFac13xuJboaLTY\nuL0KTpKhFuwxSK8c6ipe57g+aEHS7ZOyesvZ3tBu36g0ohlKoGeVD6d+d5yJ\nvdsXmiI9VSM7mVNmdexf54AMJxPLUspo135DaLbMnp2eqSexu5ZFEtoVbJ73\nIm7wB7LuGiNDJOpcIvfnwPd3wlg16mymwuWvsCRfv/6sF4fEDzaobzdelse6\nCWJ63trORgFc3bM4U2Ygt7PbrgI7oEUN+OdpL8L7jHZNHFtdTyrEittx8Kj+\nRscP\r\n=8voJ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICTNbggWtFHagrKxGiBO2awts/JTMDyGID9ROGMUAVY0AiEA4qxUP+MVZEwy6SSQrEIT+TCvv2NPEs69vAuPpfJ9Iv0="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.37_1627785521880_0.40551792908946527"},"_hasShrinkwrap":false},"0.0.34-next.38":{"name":"stagnant","version":"0.0.34-next.38","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"6970c4a806d011d42f7a13ba7a062b24ca3cf988","_id":"stagnant@0.0.34-next.38","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-gn9mz+pO/t0/lKCuqdKpLx+G46mbpTouRfph16Yxdp9jIPaGnj2dRe4WeXpW1GeZdmAi51s9aAl/MmuI36Z0mQ==","shasum":"98e0a02f901f10742343b3716edd62de6cc5d8f3","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.38.tgz","fileCount":14,"unpackedSize":155591,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBgm7CRA9TVsSAnZWagAAUIoP/0M0WLKLUVSrkUuFiHd7\nAKTVvjh+oRSfyjrjBkGu5Qp0O2671mDhdAtOB7ZwFUMdNmcimWN0TOAFNnOo\nn2yi8PQ82lZQQ48SRDr09S8gGVn/xeUC2ZU72LVe7kzIFBBlLthqB6FFhQft\n0VIhT/0VcAeq7FsQDTvfbJlbPCQa4jVFfiWnFOGDGVFx/0R0xEILE2j/t/Lf\n6azhzh6UrA/obwLvmckKPPVh+qp5oJlRl+jp3Eei2m1QDSV5CRBsvsQPlTAn\nFRXBYYSxkvzrvuSP+lmmpzi6RuF5DwHuDFC7Ww73Fot6AnYzawMwPYVmi5sw\nd6cuLSUj3N3hT1P4FFgZfW7rGp4T3I8FnltUjLWd8ivtPyjvI4qRCHLTPlBd\ne5Nrvns9l3tTG/S2oLNV/MBUl99yA7h+6L9STxRIDu01wSqidYoHEIUqjqCh\nH7CPq1KArlXIkpnkTdfsuL6/I4fO3S4fQV8Rl6lQ8BP4Dq5cRvUy6rBK8Mgk\n4XZNpNMCN015VEfMwSFLR+lEJK1//K5aaq0TSw7duSu3rSY/7uEGJu0gXZCO\n8JZNH3GcCgjpYU/4cV5/JksbvCpIatn6+3sVHubOO+zXa3SFBnh2FtZ/mQuU\nyVRhDLhanCqfKTSEbCsmjas2ZpNSUs5BfY+0bU+hewr6TiC8zvTdW7zlGuKl\nrlTI\r\n=AU6o\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGs3HKmEbtx848S57a4umo9+/6FN7sGomvJvHSQkI2QTAiEA72JjsHi/mamYd3A4VZefcRreP9wRHXfZVSzcQVxeDag="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.38_1627785659368_0.9530985532641112"},"_hasShrinkwrap":false},"0.0.34-next.39":{"name":"stagnant","version":"0.0.34-next.39","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"fdde416ac5c305a7e8c90752b113bd26b8d1e3b8","_id":"stagnant@0.0.34-next.39","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-1CmSHY+GMdTa68xzm+zIXlCxWOnKFsW0xKXznMUleLtmS+5EMHZIV4GqZ3jtvCz0BWBBBrk4BU9todjhDARDQg==","shasum":"6dda7bb412cd771b4e76256cbce610062d5821ef","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.39.tgz","fileCount":14,"unpackedSize":155758,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBgq+CRA9TVsSAnZWagAARzsP/0PziLGGNWOzlKhA1kzH\nYyGS9SrlQvyHCQJyyBtXT88Q8xWIoKOvadExeBdZxAckR9bm/8ppbbZ0bHnK\ngMS0tR18BiUgq9vLE1VcswrzgwiAGW/MlW6OUILCJqEuQiQ8ro9wfa13LRVq\noQLtfIeQRNJ+X9XItmenp7y+8gtsrQ1n9qsiunn0/+Zp8NZbaNDd8wMkQJDK\ndGqBOaAOfLD5HrtZqiReNLpVjrrYWyv8JkoMiHEBXLF0wjRd4sH05HwvNmFT\n1aR058rzlZIb3YoplDcUzbbmumESVslF/87gI8dlMyKRlw/3vlpYvcvjrNCa\nadbV5l4Hp/M65tl/TRq6ywogketAbqMP0fC1qAN4AmaySH3fAwXKE1GlW3Ni\nQQF3GzolEXbWbcRHqw+06TxLf99hzHL+wnAxzt+nxmy5guocJXc/FycwK5mx\nOoCul+HafA1l7sRahC6tpMtMSuZaoi2bHDOkafSsYVxwhoDTP/fwKYXUchgr\n49/cG0CX0qIOsE+YRGFA1dl5NjwhSEUMZsosiDD0nXCMoSRml/d/LCyWOw/u\n6HVHDDKPy7XsW33vzvXslVYbiM8X6829FDgmHxoJqF5IcW8hePsmYSt9VwoF\ndTSa+QY4udJtNIHnT76xPIobRTf7rFl4mf3XC3zAYJZLCxNVPrzjNAvqLxzE\nX6y5\r\n=DiLg\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDPg0N4ZfS2DJRAqGoIQsYmAQ1eLUqNdS6GZlrpq7DlNQIgIeckC/3q1a2hK3Ge2F0cdVd3YOwYrqiRs9/jPNWj1C0="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.39_1627785918619_0.48442805811734657"},"_hasShrinkwrap":false},"0.0.34-next.40":{"name":"stagnant","version":"0.0.34-next.40","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"71fb688eceff453b7e9fc041026211f3219fd448","_id":"stagnant@0.0.34-next.40","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-6zt/dP+xZEKKGOQN+6BEivuypUrhzqwbwenQ4ZtMSJr2U+ZQEh4oDHrVBZP0SPCC3JEVDNE/+SLc6u0NwwYUmA==","shasum":"41a6f28d4173dc888835532022f6ee11b4c25987","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.40.tgz","fileCount":14,"unpackedSize":155766,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBgsHCRA9TVsSAnZWagAAm8cP/jPUmRMpCOfs1V9rxzxW\nte19558WyahuLFuLoLezkyMKzwatLYryboiBDj7nkKk/ZaVhATxS4WNtZLl+\nLhSDXNJD9AZTy9lbcadI/penMXaIG9shztsJwNwWuC++vnBOJ9+bGGlsiwo+\n0OX0p0/kLutRbCz68JR/sjSRECvBOcwaBNWqbQhlQWgoG1AO2djjCuQzwNPr\ncoR9DvaaG6v+DJloXubpXgbKDhX0Pksm5RIiNcq0kxNxX+hGGE5nHmOhtCNC\nX5PYtaMYx0wNH1pfNbO5K9pzyjzvvowgqreni4hERap391OdXI2uuKJxyn8I\nGnI6iT6Qwxz4SjZfz5v/+EeyiDvHoeDXLMf5lTcQ5W0esGyqLQLaWuv73hYs\nLHtzrv+RCCtGf4cuSuibRdYrbK7eNzQXa/7R6hpMP0O1R10W/Jc2GSpl1UKQ\n2qg/FQSmFC01CmhNo+3TNwv6EYEjiWIoKMtZtcQ60xuZ3JBE2LCx32wv8Xk1\naKyWORK3E8a5S5bB816l5qY35BPQo8nof1k0cRUFrDqvXYQ5JQKmZxdPFds/\ncqALbyKjr/mC/75MmsMO/b4IZMpR5ywlIkK2I8f8h+Jjn0Io9VFQJYhET1kW\n/fe17LCmYIhaHV+nhp7yqfuT2X9O7kd1Qx9pzWxAtNfZUdfPmamHt95PRIXV\nuAM/\r\n=Ct1C\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBDOmwvOuaA8gnBhbQXEBCB4tdeyOI/rKVgVmn08fhP/AiAqEr8tV3z7P2I1dFVrhOAkopTgjli6TVFRUKVeoYeUiQ=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.40_1627785991649_0.39380981483912336"},"_hasShrinkwrap":false},"0.0.34-next.41":{"name":"stagnant","version":"0.0.34-next.41","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"78660d9550a1ad3e61e95c0a004cffdbe726992a","_id":"stagnant@0.0.34-next.41","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-+VVvKyVqBFMWKz6U8jW/X/m79peLcY2ekeQLNswyxzSnN3+bTVrDtiuPCs3deT8oBdrzB0a4ZhEhPl5AOzO9FQ==","shasum":"b5f4ca3b52e8bc76a918272b770bd4efd60a5798","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.41.tgz","fileCount":14,"unpackedSize":155920,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBg9pCRA9TVsSAnZWagAAHWsP+QDnYcp4ARosk0DFxtGO\nfCTJ6RxjFwJS/yaZB1wXU3g/61BBuxpG5essO+cIul+Fj8FaLzXcJ3/socCE\nEdlT6v860hDmPKFglSH8Irg0/aFMdahxhHFEOORaPlMOcvbbqAusrinadJqp\nSHcd7d0PYLWcPtOa44953+2oD7hvW+sIDznMsCyMXNJ2zibYNwERC7IaZlUz\nLnH+ml8wgCtCjcwxTZ/k73dw3ImKMGSMGtd+ogHzWBzz3Wh2mYYwcqyux36o\nw1vwLfEIS23kf3ZLdsq8s7ZJj02ogtR4FKDdR+POFvX9JfXG86FRazdsqSBd\nQkv6rkS3g0V1cAPu7bPJkGb72d1O2pyom1FRwfiby2qtBreS8jRaRJnMEkSP\n1a2b+pGJasQeF0A6Hq4Z8yQ8QVVHgDTWPHAwraTzjEzha1V3DF5iBAZ41BqL\n8jx1gAOnNrhP9aORB17fsuXn46W6ivJHI+ncjTbcjJtf1tfpLkrFp0vEnYHh\nzGfveDYvtAzE+V+F8/D8TbcA+O35XQdBwX+8xFk5/7r/K9JYCd9JfQ9Fa8/v\nna5n1hXUPgXQDzjxCv2DXzcvnhPAJEAsFLB3amGNhkGSwjUAzAcEt2VyXmgD\nulwAIBJAotDRDRM+/0On1skmuq74z6WVjPpQz53BcK+JzQtGBS5WWJtUjMP7\nT0Le\r\n=P5WW\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGW0ju+KHCf1O3gPPIxLQlsLhSPJj5q82RbueMJYTcSQAiEAo0H4ZPRS9J8JJQEIFKFpmwFSQoOu8hPR1sHh0tDgsg0="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.41_1627787113851_0.4327919896866974"},"_hasShrinkwrap":false},"0.0.34-next.42":{"name":"stagnant","version":"0.0.34-next.42","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"45e320c8eabfa967d193ba7f9f720479064d3ea9","_id":"stagnant@0.0.34-next.42","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-kmGWySFbeIclC/W3/hZ6ZAyjuEtLHyFJU+aaHZOCITvE+jT9lI2afYs6VCHbhVVQ2MZb2Vy3Lvv+bW0rM7g1ig==","shasum":"212488e8c75cd7725796dc74dd06f8ea5ff4e26b","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.42.tgz","fileCount":14,"unpackedSize":156626,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBhA9CRA9TVsSAnZWagAAefMP/RoH2HFAwberwAA90MUc\nBpyp4qEhKfu6bwzzpC2V4EydFcQ28kS539HBjYc58QZ+ifALZV4XrbxEx6oU\n7pH+VKytWw27RKzI9CGF/0niPrFyiyF1gMmyNU9RppuT0hwq1RcBnHiN3cH4\nQrv5bmP7FVHs3UOQmvgZiJ4Hfcja9/rX/HKZwCh7WrGiI1dbOIeugpUIFVX0\nhRt2t7+aBJC+PTkbK3ShmHyEPJ4/lDQBJHNBTGb+vw6g/B6k4l5jJ5IOSsZd\niK3CC1nikrmiAlnC0skkl4UQeDmoGaI2VkDZN591HpNeAgV4U5xn+ofhzE0Y\nDmfVktr1B9/D1Qw4Y1J0PQo1T+gh0zgifyLrmI0+kIJt9vB2B8AvXCKEpHUX\nrUSSIlr7wXc5bKhh/riXIO/2XbYkUWCKm1yoreqXo25oS0voTAae9Wfethrn\nOVM3ns2Q9G4vrWXIKeqPPW9SpLQpoNrsEMpQcYWgVfEvLLpgSwMnq3eaqD4g\negVyc8UhzDd+eHLTMJYqnSK0+5RjOT+nXer4RbExx4FQIUsQUbPuKi0/atV8\n0rx9q/vecLpzRF5f/ZFPNxkzK+S05/4Hrx437P361aX+D32Nm9mEqE2tnbAJ\naIxVpxcmLBm/hpm6X6du7Yrz2kkVgtCzEeBGPZ7ueYOuC0mBNrJpj9pXuTWI\ndT6i\r\n=y9Lf\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC1F1LhPvNhtogLtFVRSAYse/nXPZJLJCA4++1mHCQKPwIhALgs3mpYwgVLMYr0YI5aF98kOhpkVvVTB2+5+VNlpl2r"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.42_1627787325423_0.18228846425576917"},"_hasShrinkwrap":false},"0.0.34-next.43":{"name":"stagnant","version":"0.0.34-next.43","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"71eedba75053f216b04f86ff47328222c9a78db4","_id":"stagnant@0.0.34-next.43","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-VezNH09ZTjj4AhVBwW3snOpVbYT5IJDu0Blg1WNhtaGcjh2376/eFtGo1YCHa24iKn1TSqmm/OF4FN1hsxH26A==","shasum":"1b3b7baeafaa2b885dec24cfe48c837e39daac07","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.43.tgz","fileCount":14,"unpackedSize":157110,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBhVOCRA9TVsSAnZWagAAYDwQAJgOaYwrfvS7kuQTkGGJ\n/d1rLuCMeqD0Evxr/AV+4KkaOdafRt5B1mwrML74xeEXWKW+DCpdQARaqwjt\nuqpshxbeX+vytVahCCWyCFVw2yRFoH7lOPZvq2/ZBtBaEjw5LuVDaGz1mNX2\nQlI2EB2ijIcorL8rMDNWasLr8v7ZuVHKAMcsxxavmHiJbL61pn4T1wSZZXNX\nAPKLZmU8Tk7szhfZfg2RUu2A+ZOHyEb0cGJjWiiAXm6BwCbd46tt8brnb8tu\nBRaau2WRVxvYgBu5VK7NPvW9ZunubsysPFwO/Ecqe4wuGgZUbQktbzJQJCTw\nrCOcdLrM5BNhncvivRU7rjfOzRFcD8A/bb47TXvbFYwje1e6/ey1nm23Pd6d\nGIZv0AmkO5HxHqolfh5OJiXI8lf+C8SRX01Op6GL8Y/fHBedWQeABw7kaeYf\n1D34POgmc5Egnk1VTz0zuz18fSOqJtNOzgYyVK3j+p4Fdr+IM44xCS+SyhMY\nfdC9KqFYdBQ7I1/umgupy0FrqOC4yeT4VVf+6cWY2vAxh1Vpx/ay/f5KvlQf\n/j2xT0dB+W6VCFrYZ36WutX3LcWjRUy4uLEOVEPxSBV4/n4bGB8b4MiBSF7p\nXrdtDyAAFhuhmBjY0vp45/DZsBeqHGYyI6/ta0Cxwh32lZzIcGMLTXDhS+Vj\nBtkF\r\n=jeFr\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDWcgD/CkfG+71s1m761Xi/dWcAUFu1ROWdtaSPmVQudgIgcK3f3YZex8oUWrVJf7Xg13dv3unw9SXTbZ/Omh1WxF4="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.43_1627788622575_0.4397842560116614"},"_hasShrinkwrap":false},"0.0.34-next.44":{"name":"stagnant","version":"0.0.34-next.44","description":"Measure your slow code, make it _fast_.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nMeasure your slow code, make it _fast_.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"88cf07a0adb2b50621e0e9682aefd51944e85c99","_id":"stagnant@0.0.34-next.44","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-e1d00/c3h8HJRuS/jDrMb2Dfu9KLtyTar909foXUu7LFsIMPMkk7R+jrUnU6wfV1N1v88jiMvqHlpBNA+iWSuQ==","shasum":"aa750072c23458269eba01bcca7aed725fcf8756","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.44.tgz","fileCount":14,"unpackedSize":159922,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBi7NCRA9TVsSAnZWagAAIkAP/jea3cWNaD0U0kIM4lcV\nv+auPi03w2RYOvR1NaG5iJoOhk7/8IQl6wygcw4LlhRUXHNUhbFBigXJKbmi\nkgkuzaD7NN6B3ijlnctFSRe+CJxb/zmsuj7SNLCVKDNYdPXWdm1LcM5nNQT3\nsIM9evFHTsznUSdwFVoJFMKCWkFmzZyHt+d5NuQbm8QYAjE7B+LADvgoYitu\nIpCS5Jz4bFF0n78gopRMcYkhzIoJgdnYxnXhsVcSDg7M21c+PGnYmDqBfexj\nxCg4w6EJAyzaa6XY+IfFjQKaLdAGHTMIdGXf9uqAw3sNKvfcM6/RfxQscZ/F\neTTIqval8uVoQ1sC7Ik24TxSGvMFDzORMz6lEncX0bvhCQIKMfoZAjyVnpno\nfM6i0qADvivzXQwiDMBm4x4D6qeZWD4gotU3AxAK7cARN1pGeT2HOjTNtJ6o\nF063p0B7JGlprNDKFHCiuO2u/h1agjFmi8ieTMKZ7q6UerTnfWc56HDX3uM5\n5FemhuLVM9oAzFqIjDUI0bjrbIt+jgCbCn2tjPgZuxHjSIr0Lb553A/Q0qzS\nDAZcP/hsmKQIke+x6UDpPs6QK/k4Eia2R5Aw0XO1esN5XSA2+euKrTqxnDvW\n/N8KsIc3UeeihUm+J02u3oBTprjuFjCwjuahTFpCiF3Qmn5g8nSJjahrzdmo\ngKh1\r\n=XOcL\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHsFYE6SlEitprVjmV4dUTYP0HTbjCIAOkLPn2QuIfApAiEAqCH/+vCNQSKPGjEFdvpEq2pc5JsCR08Mdr5Ywvl/Yh0="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.44_1627795148892_0.004589341071588482"},"_hasShrinkwrap":false},"0.0.34-next.45":{"name":"stagnant","version":"0.0.34-next.45","description":"A full stack, profiling & tracing toolkit.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"keywords":["tracing","profiling","observability","honeycomb","browser","server"],"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nA full stack, profiling & tracing toolkit.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### Trace\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n## How do I synhronize my time from client to server?\n\nAll times recorded are using the JS engines unix timestamp: `Date.now()`.  So timezones aren't a problem.  But we cannot assume that a browser or a server clock is set correctly.  \n\nThe browser's clock will usually be set to whatever the OS system clock is set to.  If the browser is 50ms behind the real time for whatever reason, you will see weird results in your trace viewer.\nYou might see requests initiating client side after the response has already been sent server side, and vice versa.\n\nThis isn't an easy problem to solve perfectly, but it is fairly easy to solve if we decide we can trust the client drift is not intentional, is constant, and allow the client to be authoritative.\nWe first need to make a call to the server asking it what it thinks the time is.  The server responds with what it thinks the current time is.  We measure how long the request takes to resolve, and add that to the time we sent the request.\n\nWe then subtract that time from the server time we got back in the response.  The remainder will be the amount of positive or negative delay between server and client.\nWe can then override `config.now` on the client to add that delay so all client recorded events are offset to the server time.\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"2cfc567d5c0059a9d1ef4f0db00b3d15636e8056","_id":"stagnant@0.0.34-next.45","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-iit1USYMGgKLe2Qyw95LreBm7FSExU/uFCuNzvjf+WdWNVjQunv35o79UwDnBIoVqP2sIWXR2r5gr6jaISJkcQ==","shasum":"7dfdf9610c0912b9b720d768672797e28d671166","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.45.tgz","fileCount":14,"unpackedSize":162495,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBkmSCRA9TVsSAnZWagAA3HwP/2FhmYZxHHptCG2bBetL\n1FVAXZBClo6VZJkLRa5urk6vI+vJxjhm+tfGKNhhVY6z2W2FE6FiluPzfCID\nBoCGhiHOfqAM88OUxNlRJigBeJi7+Kras+WppF3GZ0uRHWIixWhzjP63pepA\nLShsS857nGgX364uufdu+qFgu7z32SJ4DzLSNCZIZAAbEOkcsXxzt5s+sqzT\nT6YZgwqN8XRBqhjTos65TzDwxDiGrtc3UT4I57y/8gAQsyL+pyV6Z1i+5rFf\naE0kswvnoAfcHF+2zMt9TjNswKzxZ21PXP6X7ZialXUiDcJwS/w8v8ANtoh9\n1eJQnlN+9VD0V/n4o4PDpoZArG6nMHCzqSatqCAAaiGFqlp83cZybd+0VKrw\nukTnBTSJ7Lk3ijokdLzJsJhw13miJrMQoruaC+Ig0Toe1C0y6OvwFWMionth\nfTrEAMynPKgDLNFoJ71pkM3lcV4lZlcx3gjr3GzoBkex6sYBU0wVZnbSV8wx\nv5iUVsPgCkEEZedBdbvyC0dRHbzs47S/LTZb22mqwaKJaRVPYGUHQ4+30E3n\n6sqrm7TVYWkIEBT2jdRSX4udl2uZpqPZ0b2hw3VPXGctExjdulWO3sxdTlOn\n117kOytJgfK/PWWGYwfGQWA9J+b8rDKf+zpv9eIp9ltsrLFP7ttDv75tRLCG\nC25M\r\n=aQQi\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCJSOK4z0r4cV8UsUNVCRTWTHDYPr8VWBxFPgzfIupfjAIgbN+4oD5eA+avgX2w+ZjzYbAq2tYnCKYNTcZPRQ44v0Q="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.45_1627802002780_0.6044138654009816"},"_hasShrinkwrap":false},"0.0.34-next.47":{"name":"stagnant","version":"0.0.34-next.47","description":"A full stack, profiling & tracing toolkit.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"keywords":["tracing","profiling","observability","honeycomb","browser","server"],"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nA full stack, profiling & tracing toolkit.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### stagnant\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"9e21bacf96973ef9f7be01eb36177244da26222e","_id":"stagnant@0.0.34-next.47","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-+wGAA1itA+NamRSu26hjmYAMye88cbHzMJm/yEcjk80Ez3sr6Q3ZXxGCpnkEFyZNT/INmrJ4kNUF+x8f2lqxrw==","shasum":"7356bb60f61dd060a7407bef371ddd7b90c0d9f7","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.47.tgz","fileCount":15,"unpackedSize":207834,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBqChCRA9TVsSAnZWagAATzUP/3grmIPncIYvjzNMFrw+\nDPctnbgZYZ8Wk7qFleGgZYYQr1Zn6GoFixIDaNynFMDOtf53neSNn9ZP4n/N\nTU4rTHo9mSE4VuK7l/QSxqmlgherSSvOfeg8iGHST5LwmJPDXLsGl4iflUAQ\n1oHg8GyWhV9PpFmttzacp4+ctGzylrYd55/NVH6Dv9RXOXJgXRS43TJtvYe9\n40fxyJZJQM8Q7nOOFN95aRE2VRQtAq3BtEsAH2MQS1jRwNd1UE9BXk5Su+9l\n3Jk3qC4Z1GwyzgC9WTYYdYZOjJpc6HlzcOSBcPXIt1FL56KGa9UfbfgPdmOL\nZK2KK6r6Rfzzfmkkx2Xj8Jrr6fDbVgBrzPVlKthe0bxC6arkv+T0Pg5sL4qZ\nfvkA4XvkP46jIwCQdmyr4oAkk+hk6aSOSQSlQfRti9bl7DZmbw13f5/slydr\nnXb1+FG94DOiI+1OfNpCxBov/G6r7x3WvC0nQzJ2hMnTh/tJV5nHc6A0lqC6\na1M0Fo0joR5kSQ5fgeI4s8Kg2y4btlUYPbSqdj5z/SiqXHwnpjwI/V8/hqjv\nTroCyh2NlPaY6Qd6EStPvijT+EYCum0JOtVtO8FzLawkQjfnK8W5JFDh7chy\nZvEc1abuHr8+4sY9DzeqD8bX6lrxjr2Wm7efezpK36E7TB/nZf2B8LKpsLxg\n0JOK\r\n=qUh1\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCXtaWEuxeyBO+tV4N74XNUlFr2CarXahqIaq1gSn++NwIhAMh8GsqeTqqN2tHEXHMcf3IWB2vY6aOlq8NQnrEwqZDD"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.47_1627824289549_0.023825214601318923"},"_hasShrinkwrap":false},"0.0.34-next.48":{"name":"stagnant","version":"0.0.34-next.48","description":"A full stack, profiling & tracing toolkit.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"keywords":["tracing","profiling","observability","honeycomb","browser","server"],"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nA full stack, profiling & tracing toolkit.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### stagnant\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"44c807ae5120c1755900659176a8610f2a405d96","_id":"stagnant@0.0.34-next.48","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-lMg2ELIiLIi0ApeADtLg5VvP3VM5M0m9wyVO2A0sOs3F97IecgezxEzoHMRJy3aoayaeUb8LWBoKCzcLzFTL9g==","shasum":"28d7867b60581e2a164bb83aaa2ffda4c061f0b5","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.48.tgz","fileCount":15,"unpackedSize":211064,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBrG8CRA9TVsSAnZWagAAJ2QP/A0xIMb3TbbL6rl7+wEt\n2pJ4/Z86PtmMwS3HCvgPTTGqZ7kf6ADaJFz2IXWzbJ/vffkTh662xdaoYv0V\naN4G2tsZyUAX8h4MOiXQEvUpkNgPs7li0m5D3bOL53u8Pl+e5D+vtON6zsPP\nBx7SESzfLrlI00b9Ci5FSkw+QhKizhzwB83xxcnbYZ8sa71yypusWDCqM9/x\nkN2PPYfr39I9uhbMk2Z23GHV87SSRoYmul/otqj8L857NAFs/9E8SFIKFsUa\nTM1XaKjDMVG8f0sfe0gjU8SCl94fJRh/gJ3cei1L8o88IwJ3O8p+36X9QV3v\nz+9zGhtLxARku9MCZwBD5W9qZObZiHegPB6iX0zMDR53QUNguepOdNT1s9Uz\nsbZZL1xVStUQ4hYYWvRw/b7byyJ53p/AmDwWL0P3zN61LFSUykcagN/2NUuB\nfnNfXv+/iuQ6/oFwcbxziCppe7EwLOMOjZwcGsFjNXdXKmhJkqSB2nUGS9d1\nqF5+I18Nf1bHevXbooXPiBUXlMpPDjUBfww5i+IqKi7RqOvTYqVt/dJHl1PH\nWQsCp2laYyI3HyYM8GVvbB35qSxtJzkrJR9j4BwvX/C5TuXYeNR29p8X32s7\nYw6pVKLcuP0gJ1Os1uInKnVWWAMDMlKX7Nua3guSn+gZSijjSE9K++nLxJXg\nTlD+\r\n=kmrD\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC26caZCQmTxQ1VdrQHg4Lc35PMdc0vkd4l2c+JNwVECQIhAMs6qyXKAliSraYuOHF13bSzmpLMr4nJE+3KMR6ccp79"}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.48_1627828668641_0.9688836740958131"},"_hasShrinkwrap":false},"0.0.34-next.49":{"name":"stagnant","version":"0.0.34-next.49","description":"A full stack, profiling & tracing toolkit.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"keywords":["tracing","profiling","observability","honeycomb","browser","server"],"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nA full stack, profiling & tracing toolkit.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### stagnant\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"12bda9cbc1a6b23c9ea58a3bb4dca0161d0cbd00","_id":"stagnant@0.0.34-next.49","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-M8FA3dJBnamJdi4OcECfdsHWQykJc0IcOo2t4mBC6+D+QhCTRKSOkLaIvfpNmO26cgzjJ55fsv580/DMKAKx+A==","shasum":"9ee6e57cbd52a560ec8e7b66dafbd2338d801158","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.49.tgz","fileCount":15,"unpackedSize":213031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBrQjCRA9TVsSAnZWagAARawP+gJWk0mIJWG6Xs5sMaJj\n2JX03M1DauGNP4T3q0FMLi/8+GQYimOlk0cMaFtfFhrp1SM0qquL+uTrDhoM\nV9Qa70um3FIS+irP9wxCRCJgVZm+u7QApM3KT/zD9D76CELc9kTvuoPvgZAZ\n9TnYTr67VOUs3cCAMCDOJdlVmcdGKXjsW6eo+kOrosNEB5tcte4RsGNMZiU2\nGDrRLCgQG1ikmfwr/6VdOE1Ek8bHl+6cb8RuCTENHjsd6AYBNOckNOHA585y\nWN1ashCBZbqL6D9vWKjAI4NSAHF5N+DjL/xrCUrkTKgB/AplGVxyHx/7ekfq\n+vUROuLo43gxI+700jfXrOgNQwyQOUmNdcuAhqvTV9oRYdVgiSubbzMhg618\nFXjERqIcZe2LSzzvwfv7oVp6h7NhQvxhIUk8tHeDxAp4axtzv3jxbH5TXjUh\nFevvM25Xjg/G+Aa2pBla17LjKLhzjEpw4430G+PGlblhuHC3zrIzsQpp6DJ0\njmAQg8Ry9TogsQ5pnpJK6PireK/r4GShatmikKJrCfQcTOgaGbZ0DogbLzwa\n+nOIvtj0EjrTqGcCpmBkJbrQUuw3oqPust+cJMAXKI8nsWA3a/xT+5cmLCz9\n4TGhyBa6WvOJx+83BippXG9CTSCvH4IIKwDQoZDwwg0QhYnUz1f7GLNSkONt\njFfv\r\n=MW1f\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDhyB9lG8azypKtfKnsZFTmLQOL235ug/F3klrSYzlYmAiBdGp85qnrCc4kLUKnm/LyDtZpJLjMNn0/bYDOGbvy7eg=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.49_1627829283611_0.9789244095379077"},"_hasShrinkwrap":false},"0.0.34-next.50":{"name":"stagnant","version":"0.0.34-next.50","description":"A full stack, profiling & tracing toolkit.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"keywords":["tracing","profiling","observability","honeycomb","browser","server"],"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nA full stack, profiling & tracing toolkit.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### stagnant\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"ad51e37051382ce320e3b60e7ad98c352e85d55c","_id":"stagnant@0.0.34-next.50","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-OWuMJ58jGUNBBI4SqR0lSGwIOWX7nrs6oSSRGAZw1Ts3Ek4YsoUqQD123Mc6MQ9F8TCrPf/tPPiO12cjhFFpLg==","shasum":"1d68cad1379ea683fc4680bca42507db3e9bc7a5","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.50.tgz","fileCount":15,"unpackedSize":207983,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBrV/CRA9TVsSAnZWagAA9B0QAJA6OfBHVm84T4leJ2yh\nQGkWAi00hH/uTK4btB6qvmyivAlC2Wr02Cs5paibpIJ5SFoq5Y9BR6NWdFHz\nkcOFmm0vx8VvSNfCrVUdP98YfMgWBqxKAptN4tnxe6QFUAkn3Su/RLJQNB6z\nJLN1s8dUqEJvZed5Wzj/iaDzaOLW++AoOma1oJPH/GggKiAasrjLPevAZSPC\nLmuesy2gaAsFN7uVQ4YPskTWgvFduJzWzEAQ47cGAhzg5j4kgvsRucgESWRD\nPymP3vBDNabrSQ/yTJwXx5RZcKnP61sUL1pr0EDbe/dZRdxffZ8XLNZBgNAP\naIaCoY3V37tX8/MkQlDL5XBFyxmN+/L24se93tIiD5/EwAoDdkzsUPGPUY/g\nsITfE53bOmjsxVllqSPk0US2qvH2Nf7/6ZsAJ/qvI6jnJkJfUPBSwH75qLjf\n+4okxAT7e81PsW5gUoQpG5/UJM2NoU0j/wmZMStpemokSxqEp95ch3ggP4EN\nlaqMC6WVGmzpFqJqCu9swNTSwJpZGZejrXtijlNCaIZmHaW9KAhZPO/h0A7g\n+Czn0fyPedOnnMlPKGXSgpGEFnv5KRjxPkMOUMdl5VBACvAt6Mla6j5ktp3T\noGFggCZmlt7o0s6xALzCQFww22h3KBYzxGb8C6BkxwxHBD3uIYqgPZAwsk//\n1KJB\r\n=MtOv\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFb8vJlpcuMoB4f1qH/T3nmZrzPwNCVFgenvyrsH9W3OAiBX5n8Kdzm30ApYBU+HaLf6TG83MCNuJnf483jja+WfzA=="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.50_1627829631479_0.012917039251234108"},"_hasShrinkwrap":false},"0.0.34-next.51":{"name":"stagnant","version":"0.0.34-next.51","description":"A full stack, profiling & tracing toolkit.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"keywords":["tracing","profiling","observability","honeycomb","browser","server"],"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nA full stack, profiling & tracing toolkit.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### stagnant\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"53f8ca3b9a131f149e81449a6fd8b30a42f05729","_id":"stagnant@0.0.34-next.51","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-PiTZEhybTxPYPUBhIR1bO46DxeQLBRy2XlqQEdQBOaeqI2Q55THYfCH8SFKaobu46cL2GY22axohlbHXFFPG9g==","shasum":"d40c85cd0b09562e5addc99ec2008e9d45832e2a","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.51.tgz","fileCount":15,"unpackedSize":214083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBrcwCRA9TVsSAnZWagAAxWsP/3dH7Lcu+z+J6X8x+o2I\nn9EYBvs3iRyuK8nDI8YoCaf+9fwvSZeX2h/xdhr2mItBOYWNDOylJqRKiRW9\nIxueiHPE+gB5Ytw2mq+TpUNJaBnj5ckIeAHXk8wraoD6dmyEVYRlM3+J9TOw\nsejt3LYXfzX4ULGgi2JISXxBWVRowwaTugr0IKv/vFvpevGN5qVpl5JcH+MB\n09ABi5Mse/WvWcjCEnnt8kLf21r5BVeMD7Afrit7YJHI3Q2htF/eN0HQcpqD\nbWTdDf3Zfc0rF2kG7XhHpI1wK+6kvHDeLLthLqR/z1EKmm268Yjpgl+E7vyl\n6qY0QKDwJ+Co1QzQXS0GKXttDd3pyT6U6anhL1gEfMLimzWLvEDnfzaG5irU\nZ7P/KYh07hx1oC8lavuYZUqcnl7i1gNERKSrNHZxww3ZEsROILfCn2wgSYL/\npRWocs2uM0nUGyoHTYDezKVK/Zj+tkDROjg3eOPgINnvAehhj2L8LBXpXeLb\nnW1iZ8EAsVySdqozkWEMBn/4nvtO+vIljoVI6zGVkvczo0JYFRltoOkL5w+S\nVGfxi/u+WwUpXSJsN/o1JWM/75R1Xb33DYs94A5LcT+5qHYjA1golfzxBj/m\nNxAflHezSODNQGlNQ3njKoqP6XK1QwfZlUHyotuZWB1uUP058DgIjWlUMSdt\nCa2U\r\n=Xxp3\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDp4/v7wFQIxd0Z4nCTUCmTCRmLkh2Z3WLoXICoNvM64AIgJ88eP071fslHTwwgXed8fRiDlGqub17o+Q/vP7YBYhA="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.51_1627830064118_0.8326204901238698"},"_hasShrinkwrap":false},"0.0.34-next.52":{"name":"stagnant","version":"0.0.34-next.52","description":"A full stack, profiling & tracing toolkit.","main":"index.cjs","module":"index.mjs","browser":"dist/stagnant.browser.min.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","prepare":"rollup -c rollup.config.mjs"},"engines":{"node":">=14.0.0"},"keywords":["tracing","profiling","observability","honeycomb","browser","server"],"author":"","license":"MIT","devDependencies":{"@babel/core":"^7.14.3","@babel/eslint-parser":"^7.14.4","@babel/eslint-plugin":"^7.13.16","@babel/preset-env":"^7.14.4","@rollup/plugin-commonjs":"^19.0.0","@rollup/plugin-node-resolve":"^13.0.0","dotenv":"^10.0.0","eslint":"^7.27.0","nodemon":"^2.0.7","rollup":"^2.50.5","rollup-plugin-gzip":"^2.5.0","rollup-plugin-terser":"^7.0.2"},"dependencies":{"node-fetch":"^2.6.1"},"readme":"# stagnant\n\nA full stack, profiling & tracing toolkit.\n\n## What\n\n- A JS instrumentation library for measuring execution time across the stack\n- No inference, 100% explicit instrumentation\n- Easy to use - integrate any 3rd party metrics service\n- Example honeycomb.io integration OOTB\n- Browser = 0 Dependencies\n- Server = 1 Polyfill (node-fetch)\n- Runs in the [Browser](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoABAHMotAEYYocKULi1YFKXBjEAKhBy0ArsW0AHAE61q8VRLEAdNGBNpqxCPSkATGFAYAJ4AFFhwAJTALlKxUlZ6JlZoUmgwAO5SAAo2WBC6IVIAmlIAvAB8OnqGxmYhRdrhEREurC4ubh5ePlYYaL60WAAiAcFhEGilAAyNGAAepQDMU1NRMXEJxEkp-oGheSkA1FIAshjEhPi9-YMhEVLSWPNSALRSBy1obWguGHBBHiknU83hSFgwVl0AFEAG4iO7RFJxDDpDAQYjxPoDYajUKfb5-AHUIHuEE+OFWCBgIJZCHYOAI9axMC0KyFWAYiBlKRTADcUi5AB4pABGGAAVn5XMOh3uiLiCtO50u12xdyZUm+BP+gOB3RS1GUUAAajBKZAYL4QjA4Qw1kjYuoMcApBZNdybSINRYQi6AOQaHAAYSNpvNEEtQ3OGD9iCki01nw1noY3Is+B11EKdzKlQpVJpdPC1ttxHunwVTqkpgxpSkKLRGJ9Un9hqgUCjxBjcYALJrtDmKmojSWROWNZtttWzK12phMySuqDh+3R3b5cjUejMTccXt1V854TdaT9Tou-0Ib4AErwExQYiMh31rcY1WDEb7-G-Bd65eQB8zVvOB72IBl7QVBtt3fPcxm-H55yJRcyRSABVOAMBkGAn0reg4Cbbl8MwzAGF9DUFXoFNHxdTAcG0JhfBqGBtCIqwDCMGBEw3RU4gGagTBwBh8AUWhfCCfAJjSKwAAl9BOAAZKRDjrEJyJ4qQAANBQscoABJgFoziAB8jKkP1iFoLsoD9VgpH0himNec8IXYnBWEFMQdI0tSFQrdTvgVVgk2fDkpCo7lgACuJwrrKCm0KP1wUhGBYREP1tH9JLoVLTsMHwGA5mwCxYFjKQACZ+ykQdKiylLSzXMspD82JQoSECH25OLXUKN0hzbE0zSpCMrRdfAxqoyxEyaic7w62LX26qr7iHIjLysG9ZsfCb4k28dnza0DOoW5tqqBaBGCsYDQIZbaDofPbvXwMAoBMOBCAPBVJ2SHb2uIWcEPQzDsM+fBDWIah3rC3MWw1dRNBgfKrBsNkYGa10bDsOA4HyuZ0RCEV8WCygQF0WAUIQHgexFRAADYRTYDgQEMvBQax4mhEYZgeDYABdKgoAmABrCnUCZrg8CImQSNEKgknIHgSGICw4EQMQxHcCxBZkUHBjESXpYAASmfBjcWMRfHyYg9a7KW+mIfABN8fB+GJ4gggsbgSeoSkLFEVgedYIA) and the [Server](https://runkit.com/jaforbes/stagnant-server-side-usage)\n\n## Builds\n\nYou can view all the latest builds on unpkg [here](https://unpkg.com/browse/stagnant@latest/dist/).\n\n### Browser\n\nStagnant is built to trace performance across the entire stack.  You can begin a trace client side, continue tracing within the server and then pick up the trace client side again.\n\nThis makes it possible to get far deeper insights into your Time to Interactive (TTI) and Time to Load (TTL).\n\nThe browser version of stagnant has no dependencies, it simply uses the native fetch module to call out to honeycomb for each event.  The node version uses the same code but relies on the `node-fetch` polyfill.\n\n\n- [Minified UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.min.js)\n- [UMD Stagnant Honeycomb Module](https://unpkg.com/stagnant@latest/dist/stagnant-honeycomb.browser.js)\n- [Minified UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.min.js)\n- [UMD Stagnant Module](https://unpkg.com/stagnant@latest/dist/stagnant.browser.js)\n\n### Node.js\n\nDepending on your project structure node.js will either import the native ESM module or the CJS build automatically.\n\n- Native ESM Stagnant Module: `import stagnant from 'stagnant'`\n- CJS Stagnant Bundle: `const stagnant = require('stagnant')`\n\n- Native ESM Stagnant Honeycomb Module: `import stagnant from 'stagnant/honeycomb.js'`\n- CJS Stagnant Honeycomb Bundle: `const stagnant = require('stagnant/honeycomb.cjs')`\n\n> 🤓 If anyone knows how to make `require('stagnant/honeycomb')` automatically point to the `honeycomb.cjs` file, please let me know!\n\n## Quick Start\n\n- npm install `stagnant`\n\n```js\nimport Stagnant from 'stagnant'\n\nconst traceOptions = {\n    onevent({ id, name, data, parentId, startTime, endTime }){\n        console.log(name, endTime - startTime)\n    },\n\n    // Advanced Options:\n    //\n    // onerror(){},\n    // onsuccess(){},\n    // onflush(){},\n    // generateId(){},\n    // traceId,\n    // parentId\n}\n\nconst stagnant = Stagnant(traceOptions)\nasync function main(){\n    const I = stagnant()\n\n    const package = \n        await I( () => fs.promises.readFile('package.json', 'utf8') )\n\n    const files = \n        await I( 'ls', () => fs.promises.readdir('.') )\n\n    for( let file of files ) {\n        const filedata = I.sync( 'read file: ' + file,  () =>\n            fs.readFileSync(file)\n        )\n    }\n    \n    await I.flush()\n}\n```\n\n## Demonstration\n\n\n```js\n\nimport Stagnant from 'stagnant'\n\nasync function main(trace){\n\n    await trace(() => sql`\n        select expensive_query()\n    `)\n    \n    // trace is async by default to avoid Zalgo\n    const file = await trace( () => fs.promises.readFile('package.json', 'utf8') )\n\n    // for sync code, use trace.sync\n    trace.sync( () => 2 + 2 )\n\n    // add a meaningful name when the inferred name won't do \n    trace.sync('sequence loop', () => {\n\n        for( let x of sequence() ) {\n            someSyncCode(x)\n        }\n    })\n\n    // create child traces, great for modelling the callstack\n    await trace('somethingElse', p => \n        somethingElse(p)\n    )\n\n\n    await trace.flush()\n}\n\nasync function somethingElse(trace){\n    // this trace is a child trace 👆\n    // you can use this data to create useful graphs with e.g. honeycomb/open tracing\n\n    await trace( () => sql`\n        select another_expensive_query()\n    `)\n\n    // by default, names are inferred from the `toString()` of the callback\n    // but you can specify a name explicitly as well\n    let data = trace('custom-name', async () => {\n        return anotherExpensiveCall()\n    })\n\n    // you can also attach metadata to the trace (and any child events)\n    let data = trace({ name: 'custom-name', x:1, y:2 }, async () => {\n        return anotherExpensiveCall()\n    })\n\n    // passing an object with no function, will attach that data\n    // to all subsequent events\n    trace(data)\n\n    return true\n}\n\n\nconst events = []\nconst options = {\n    onevent(event){\n        // capture all events\n        events.push(event)\n    },\n    async onsuccess({ name, parentId, id, data, startTime, endTime }){\n        console.log(startTime, name, 'duration:', endTime - startTime )\n    },\n    async onerror({ name, error, parentId, id, data, startTime, endTime }){\n        console.error(\n            startTime, name + ' (failed)', 'duration:', endTime - startTime\n            ,'error:', error\n        )\n    }\n}\n\nconst stagnant = Stagnant(options)\nconst trace = stagnant()\n\nmain(trace).finally( () => {\n    // all events ready to inspect\n    events\n\n    // we've also been logging our events as they happened\n    // we could be sending them to a tracing API if we prefer\n})\n```\n\n## API\n\n### Stagnant\n\n`Stagnant(options: stagnant.Options ) -> StagnantInstance\n\nInitialize a trace.\n\n### Stagnant.call\n\n`Stagnant.call( trace?, ...args, () -> b ) -> b`\n\nSafely invoke a trace callback even if the trace is null.  Fairly useful for writing wrappers around 3rd party libraries that may be invoked by code without a trace variable.\n\n```js\nasync function something(event){\n\n    // instead of:\n    // await event.trace( 'normal invocation style', () => db.query('select 1+1') )\n    await Stagnant.call(event.trace, 'safer invocation style', () => db.query('select 1+1') )\n}\n```\n\n### Stagnant.ensure\n\n> Stability: 💀 Unstable\n\n`trace = Stagnant.ensure( trace? )`\n\nMuch like Stagnant.call, but instead of immediately invoking the trace (if it exists), a mock trace is returned that will execute just fine but will not actually create any events or traces behind the scenes.\n\n```js\nasync function something(event){\n    event.trace = Stagnant.ensure(event.trace)\n\n    await event.trace( 'will work even if event.trace is null', () => db.query('select 1+1') )\n}\n```\n\n### stagnant\n\n`Trace` is often aliased as `I` (for _instrument_).  Usually you invoke trace with a callback.  stagnant\nwill time how long it takes for a promise/generator/iterator/async function to settle.\n\n```js\nconst stagnant = Stagnant(options)\n\n// starts a new trace\nlet I = stagnant()\n\nlet output = await I( () => myAsyncFunction() )\n```\n\nYou can also measure a synchronous function via `I.sync`\n\n```js\nlet output = I.sync( () => mySynchronousFunction() )\n```\nYou can attach data to the current event and any child events via `trace( data )`.\n\n```js\n// all child events will have the url and method property attached to event.data\nI({ url, method })\n```\n\nYou can also pass in data when setting up a callback to be traced:\n\n```js\nI({ url, method }, () => callEndpoint() )\n```\n\nBy default `stagnant` will name the event using the `Function::toString()` method of your callback.  But you can explicitly name an event as well via two approaches.\n\n1. Passing a string as the first argument\n2. Including a `name` attribute on the event data object\n\n```js\n// string as first arg\nI('call the endpoint', { url, method }, () => callEndpoint())\n\n// name attribute on the data object\nI({ name: 'call the endpoint', url, method }, () => callEndpoint())\n```\n\nYou can create detailed call graphs by taking advantage of the child span constructor passed in to all callbacks:\n\n```js\n\nI( 'outer', I => // rebind I\n    I('inner', I => // to create nested traces\n        I('core', () => ... )\n    )\n)\n```\n\nEach event will have a `parentId` set to the `id` of the previous event.  This is great for explicitly modelling async call stacks.\n\nWhen you are done measuring your code call `trace.flush` to signal to stagnant that the trace is over.\n\n\n```js\nawait I.flush()\n```\n\nIt's best to not use `I` after calling flush as the total duration of rootEvent will be less than the summed duration of any child events... which would be weird.\n\nKeep in mind, `flush` doesn't wait for other events to finish that is your responsibility.  Generally call `flush` in a `finally` that wraps your entrypoint.\n\nThat's a high level philisophical distinction in stagnant with other libraries, there's next to no magic, just a nice pure dose of sugar.\n\n\n### stagnant.Options\n\n#### generateId\n\n`() -> string`\n\nControl how stagnant generates identifiers, by default `Math.random().toString(15).slice(2,8)` is used.\n\n#### onevent\n\n`async onevent( event: Event ) -> void`\n\nDispatched whenever a callback settles, whether it throws an exception or returns a value.\n\n#### onsuccess\n\n`async onsuccess( event: Event ) -> void`\n\nDispatched whenever a callback does not throw an exception.\n\n#### onerror\n\n`async onerror( event: ErrorEvent ) -> void`\n\nDispatched whenever a callback throws an exception.\n#### onflush\n\n`async onflush( event: RootEvent ) -> void`\n\nDispatched `.flush()` is called on the root trace.  Calling `flush` implies the end of the parent trace.  This is helpful for measuring overall run time of a trace.\n\n## Event\n\n```typescript\n{\n    // id of the parent event\n    parentId: string\n\n    // reference to the parent event\n    , parent: Trace\n\n    // id of the current trace\n    , traceId\n\n    // id of the current event \n    , id: string\n\n    // event metadata, just a state bag with no schema\n    , data: any\n\n    // time is represented as a millisecond unix timestamp\n    , startTime: number\n    , endTime: number\n}\n```\n\n## ErrorEvent\n\n```typescript\nEvent & { error: Error }\n```\n\nJust like an event, but with an error attached.  This will be dispatched to your callback whenever a callback throws an exception.\n\n## Advanced\n\n### Logging\n\nBy default stagnant calls `config.console` for all logging.  These logs functions are just empty functions.  You can enable logging by specifying `console: console` when initializing stagnant.\n\nWhen initializing honeycomb configuring stagnant options requires an outer key `config` so `{ config: { console } }`\n\n## FAQ\n\n### How do I disable stagnant when testing offline?\n\nThere are several ways, but the easiest is to override `config.onevent`.\n\nYou can simply replace config.onevent with a no-op function, but the events themselves are useful for testing, so you may instead want to replace `onevent` with a method that pushes traces into a list that you can examine from your tests.\n\n```js\nlet events = []\nlet stagnant = Stagnant({\n    onevent(event){\n        events.push(event)\n    },\n    onflush(){\n        events.length = []\n    }\n})\n\ntest('Sign up email was sent', async t => {\n    await callApi()\n\n    let event = events.find( x => x.name == 'sendEmail' )\n    t.ok(event)\n\n    t.ok(event && event.response.status, 200)\n})\n```\n\nIf you do not want the events in offline mode, you can also easily mock the stagnant API by passing `null` to `Stagnant.ensure`.  This just returns a function that mimics a stagnant trace function, but doesn't actually do anything except execute the functions you pass in.\n\nBoth approaches allow you to keep your traces in your live production code even if you want to temporarily disable them in certain contexts.\n\n```js\n\nlet I = Stagnant.ensure(null)\n\nI( 'trace', () => {\n    // hello is logged, but no traces recorded\n    console.log('hello')\n})\n\n// this metadata is ignored and discarded immediately\nI({ a:1, b: 2 })\n```\n\n### What is a trace vs a parent vs an event?\n\nA trace is simply a group of events.  All events sharing the same trace_id are considered part of that trace.  Theoretically you could infer a relationship between events by following the tree of parent/child events.  But if you set only the parentId and not the traceId, tools like honeycomb will get confused.\n\n### How do I instrument 3rd party libraries if everything is explicit?\n\nShort answer, you don't.  \n\nLong answer, instead of directly calling the 3rd party library, call your own function that calls the library and time that.\n\nYou can use `Stagnant.call( trace, () => ...)` or `Stagnant.ensure(trace)` instead of `trace( () => ... )` to safe guard against not having a trace variable.\n\nIf the trace is undefined, `stagnant` will just invoke the callback without creating a trace.  That way you can write code that will behave just fine even if there is no trace variable passed down.  This also means you can disable tracing in your codebase without having to restructure your code beyond not passing down a trace at the entry point.\n\n```js\nconst Stagnant = require('stagnant')\n\nfunction query(query, values, I=null){\n    I = Stagnant.ensure(I)\n    const results = await I(() => db.query(query,values))\n    return results\n}\n\n// this will not perform a trace\nawait query('select * from users where user_id = ? ', [1], null)\n\n// this will perform a span within the active trace\nawait query('select * from users where user_id = ? ', [1], I)\n```\n\n### Why use callbacks for everything\n\nIt helps capture absolutely everything safely.  Everything that is measured can be wrapped in a try catch, and the execution itself can be deferred, or even skipped if required when you are debugging things.\n\nIt is a sensible default.\n\n### But won't using anonymous functions introduce signifcantly delays?\n\nFor application profiling, no, the delay will be neglible.  For something low level like a virtual dom diffing algorithm, maybe.\n\n### How do I continue a trace across multiple servers or contexts?\n\nSend the `parentId` and `traceId` to your API via a header, then when when kicking off a trace pass in those ids `let I = stagnant({ parentId, traceId })`. \n\nThey will be merged into the root event.  Usually the rootEvent has a null parentId and the traceId is generated on initialization.  But by passing them in\nthis rootEvent will become a child event of an existing trace.\n\nSee the [honeycomb implementation](./honeycomb.js) for ideas.\n\n### How do I get the activeTrace?\n\nIn other tracing libraries, instrumentation is automatic and is quite complicated.  For example in honeycomb's Node.js beeline the async_hooks module is used to figure out whether an async function invocation belongs to another async call stack that is related to a given trace.\n\nFinding out the active trace in these libraries is a complex system that isn't foolproof.  It is possible to lose the active trace by nesting an async function, or using an iife, or any other range of behaviours async_hooks trips up on.\n\n\nIn stagnant, all instrumentation is manual.  Because it is manual, you always have a reference to every trace, because you are the one that invoked the tracing function `I`.\n\nThere is no ambiguity or complex system.  When you start a new span, you can know that any code that runs inside that block belongs to the span/trace.\n\n```js\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n})\n```\n\nIf you want some api request, db query or other async task to belong to a trace, you need to pass that `I` reference around and wrap the given task in that reference.\n\n```js\n\nasync function request(I, ...){\n    return I( () => fetch(...) )\n}\n\nawait I( 'example', async I => {\n    let traceId = I.traceId()\n    let id = I.id()\n\n    const users = await request(I, 'https://example.com/api/users')\n})\n```\n\nIf you just want requests to belong to the overall trace but not necessarily nested under the span that invoked the request, you can just use a different `I` reference. \n\n```js\nawait I( async I => {\n\n    async function request(...){\n        // accesses I via a closure\n        return I( () => fetch(...) )\n    }\n    \n    await I( 'example', async I => {\n        let traceId = I.traceId()\n        let id = I.id()\n    \n        // does not use the current I reference, uses the parent one\n        const users = await request('https://example.com/api/users')\n    })\n})\n```\n\n\n## Honeycomb integration\n\n![A visualization of a call graph as measured by stagnant using the honeycomb integration.](./assets/honeycomb-usage.png)\n \n*A visualization of a call graph as measured by `stagnant` using the honeycomb integration.*\n\nStagnant can be used offline and with any 3rd party instrumentation toolkit you prefer to use.  I originally wrote this tool as I was continually having issues with the official node.js honeycomb beeline library.  I was often getting issues with missing traces, and missing parent spans and I couldn't figure it out.  After spending a lot of time on it, I figured it was easier to just write an adpater that is 100% explicit and doesn't rely on Node's [async_hooks](https://nodejs.org/api/async_hooks.html) module.\n\nSo here we are.  I share with you the same integration just in case it is useful for to you.  But stagnant can be used as a standalone library just as easily.\n\nThere is a simple [honeycomb](https://honeycomb.io) integration in [`stagnant/honeycomb.mjs`](./honeycomb.mjs).  Check out the [usage script](./honeycomb-usage.mjs) to set it up in your own project.\n\n","readmeFilename":"readme.md","gitHead":"d23beeaa141b334c6c767aaff97baa508f2e94c8","_id":"stagnant@0.0.34-next.52","_nodeVersion":"14.17.2","_npmVersion":"6.14.13","dist":{"integrity":"sha512-7EuZl0cYT+Bk8f+k6xPMPSI96uGbTGj0XBf7CqlrIdyM3PEVQ75iDUL1q9HjNLikDAXUzmK8g28Bn0Q2XXzSag==","shasum":"6e39bf5206aaa5c20309128b4786dc7662d40875","tarball":"https://registry.npmjs.org/stagnant/-/stagnant-0.0.34-next.52.tgz","fileCount":15,"unpackedSize":215977,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBrujCRA9TVsSAnZWagAAL2gP/2K7lJhyvS1CTP2kQS3Z\n7FPkehT26HiUaeyjaHci7oIDEhIrsna5LTK6Wwu581+r9eCBlmHEteTzkb5B\nPivm/1o2eDDhAH1KkXdNLUGVlbJXC3Iftz2TUtXxBZHNhqo1ajBgpRiQlX1a\nkN/5G9h6q79wIKyVqAMwJy150g37yqXowusuz+SvzN5RsuKzV2IQVPbfpz44\n3Q1keT/VScSVJKszYE9KRzB2rqfNy6Lh2hfIBeGymdMe72GpufT5aAyZlYWH\na6Sj0YwnWziE54IQbBFovh1c7yKlTT8m0qJehwy6swjtMwWwqraIZsRKvWfx\nLseiSknqgM4lpYX3m265NxbjABDVaq9O+hLUaU1n2d8AjZQv5U+swwXFlEpO\nt454K8xKHVFuWmu0KIzGJbtIgviAjHG/J3LBrHhsTpkqylpGYhqgCU58vniT\n7z/MxGAPh4cC40iZp2UcG/EcZ9GsKz1OHaiWYGLLW/CcztVzL6WlwT2h6JwM\nknUU5hZqNMSHxF/xTMwSjkUhxQS/u/P9N12JI64QOZJOkLEXq8bGkTVNSz/z\n+QuoR60x+aW9dNtq7e5r4gx7W1+xThWnul8IxtVS6nxmynMTIFicwrm2f1JN\nZPjqTcmNolj30Y01zIEG+yn/SVT/8cu9YYrFL7Al9Mose2dXpbkVrJ8FUDCv\nfLiT\r\n=s4Zu\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBHffeN4qs8wNhVjw4Z2zMAo8Oy7KqWkucDdWU7G8Oj3AiEA+bPjGSuQKMuyey855jk+V0IWQy5FYvJOd4E8+0KE3RI="}]},"_npmUser":{"name":"jaforbes","email":"james.a.forbes@gmail.com"},"directories":{},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/stagnant_0.0.34-next.52_1627831203092_0.061104062974975504"},"_hasShrinkwrap":false}},"time":{"created":"2021-06-01T07:41:36.305Z","0.0.0":"2021-06-01T07:41:36.441Z","modified":"2022-05-18T14:52:34.159Z","0.0.1":"2021-06-01T13:26:56.789Z","0.0.2":"2021-06-01T22:35:50.763Z","0.0.3":"2021-06-01T23:41:02.504Z","0.0.4":"2021-06-01T23:59:32.319Z","0.0.5":"2021-06-02T00:01:21.424Z","0.0.6":"2021-06-02T00:29:14.047Z","0.0.7":"2021-06-02T00:36:22.256Z","0.0.8":"2021-06-02T00:41:41.229Z","0.0.9":"2021-06-02T00:49:54.764Z","0.0.10":"2021-06-02T00:50:53.716Z","0.0.11":"2021-06-02T00:54:14.949Z","0.0.12":"2021-06-02T00:56:57.766Z","0.0.13":"2021-06-02T00:58:51.283Z","0.0.14":"2021-06-02T00:59:58.790Z","0.0.15":"2021-06-02T01:01:02.410Z","0.0.16":"2021-06-02T01:02:39.457Z","0.0.17":"2021-06-02T01:04:30.009Z","0.0.18":"2021-06-02T01:48:45.452Z","0.0.19":"2021-06-02T01:51:42.973Z","0.0.21":"2021-06-04T09:15:05.670Z","0.0.22":"2021-06-05T06:31:16.178Z","0.0.23":"2021-06-05T06:41:31.519Z","0.0.24":"2021-06-05T07:04:47.045Z","0.0.25":"2021-06-05T07:28:35.905Z","0.0.26":"2021-06-05T08:02:29.895Z","0.0.28":"2021-06-10T03:34:43.016Z","0.0.29":"2021-06-10T03:41:27.543Z","0.0.30":"2021-06-10T03:59:01.651Z","0.0.31":"2021-06-10T07:37:39.903Z","0.0.33":"2021-07-29T12:10:27.730Z","0.0.34-next.0":"2021-07-29T13:05:39.128Z","0.0.34-next.1":"2021-07-29T13:18:20.727Z","0.0.34-next.2":"2021-07-29T13:24:59.280Z","0.0.34-next.3":"2021-07-29T13:31:53.258Z","0.0.34-next.4":"2021-07-29T13:35:41.155Z","0.0.34-next.5":"2021-07-29T13:39:13.190Z","0.0.34-next.9":"2021-07-29T13:49:52.114Z","0.0.34-next.10":"2021-07-29T13:52:34.031Z","0.0.34-next.11":"2021-07-29T13:55:01.055Z","0.0.34-next.12":"2021-07-29T14:01:18.483Z","0.0.34-next.13":"2021-07-29T14:09:29.563Z","0.0.34-next.14":"2021-07-29T14:15:21.531Z","0.0.34-next.15":"2021-07-29T14:22:01.138Z","0.0.34-next.16":"2021-07-29T14:34:19.671Z","0.0.34-next.18":"2021-07-30T07:40:21.912Z","0.0.34-next.19":"2021-07-30T07:47:05.902Z","0.0.34-next.20":"2021-07-30T07:58:34.796Z","0.0.34-next.21":"2021-07-30T08:04:02.877Z","0.0.34-next.22":"2021-07-30T08:44:40.103Z","0.0.34-next.23":"2021-07-30T13:52:49.429Z","0.0.34-next.24":"2021-07-30T14:06:21.799Z","0.0.34-next.25":"2021-07-30T15:52:52.714Z","0.0.34-next.26":"2021-07-30T16:11:52.147Z","0.0.34-next.27":"2021-07-30T16:35:33.881Z","0.0.34-next.28":"2021-07-30T16:43:40.580Z","0.0.34-next.29":"2021-07-31T02:17:30.955Z","0.0.34-next.30":"2021-07-31T02:18:18.701Z","0.0.34-next.31":"2021-07-31T02:49:21.931Z","0.0.34-next.32":"2021-07-31T02:55:13.059Z","0.0.34-next.33":"2021-07-31T23:57:42.380Z","0.0.34-next.34":"2021-08-01T01:23:40.983Z","0.0.34-next.35":"2021-08-01T01:28:38.798Z","0.0.34-next.36":"2021-08-01T02:29:34.392Z","0.0.34-next.37":"2021-08-01T02:38:42.077Z","0.0.34-next.38":"2021-08-01T02:40:59.515Z","0.0.34-next.39":"2021-08-01T02:45:18.775Z","0.0.34-next.40":"2021-08-01T02:46:31.873Z","0.0.34-next.41":"2021-08-01T03:05:13.974Z","0.0.34-next.42":"2021-08-01T03:08:45.569Z","0.0.34-next.43":"2021-08-01T03:30:22.706Z","0.0.34-next.44":"2021-08-01T05:19:09.029Z","0.0.34-next.45":"2021-08-01T07:13:22.985Z","0.0.34-next.47":"2021-08-01T13:24:49.679Z","0.0.34-next.48":"2021-08-01T14:37:48.835Z","0.0.34-next.49":"2021-08-01T14:48:03.743Z","0.0.34-next.50":"2021-08-01T14:53:51.684Z","0.0.34-next.51":"2021-08-01T15:01:04.274Z","0.0.34-next.52":"2021-08-01T15:20:03.372Z"},"maintainers":[{"name":"jaforbes","email":"james.a.forbes@gmail.com"}],"description":"Measure your slow code, make it _fast_.","license":"MIT","readme":"","readmeFilename":""}