[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"projectName\": \"apollo-opentracing\",\n  \"projectOwner\": \"DanielMSchmidt\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 100,\n  \"commit\": true,\n  \"contributors\": [\n    {\n      \"login\": \"DanielMSchmidt\",\n      \"name\": \"Daniel Schmidt\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/1337046?v=4\",\n      \"profile\": \"http://danielmschmidt.de/\",\n      \"contributions\": [\n        \"code\",\n        \"ideas\"\n      ]\n    },\n    {\n      \"login\": \"cliedeman\",\n      \"name\": \"Ciaran Liedeman\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/3578740?v=4\",\n      \"profile\": \"https://github.com/cliedeman\",\n      \"contributions\": [\n        \"bug\",\n        \"code\",\n        \"test\"\n      ]\n    },\n    {\n      \"login\": \"Multiply\",\n      \"name\": \"Jens Ulrich Hjuler Pedersen\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/453031?v=4\",\n      \"profile\": \"http://juhp.net\",\n      \"contributions\": [\n        \"bug\",\n        \"ideas\",\n        \"review\"\n      ]\n    },\n    {\n      \"login\": \"frances3006\",\n      \"name\": \"Francesca\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/9115596?v=4\",\n      \"profile\": \"https://github.com/frances3006\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ricardocasares\",\n      \"name\": \"Ricardo Casares\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/84963?v=4\",\n      \"profile\": \"https://analogic.al\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mwieczorek\",\n      \"name\": \"Michał Wieczorek\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/7051680?v=4\",\n      \"profile\": \"https://keybase.io/mwieczorek\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"koenpunt\",\n      \"name\": \"Koen Punt\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/351038?v=4\",\n      \"profile\": \"https://koen.pt\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"zekenie\",\n      \"name\": \"Zeke Nierenberg\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/962281?v=4\",\n      \"profile\": \"https://github.com/zekenie\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"voslartomas\",\n      \"name\": \"Tomáš Voslař\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/1945040?v=4\",\n      \"profile\": \"https://app.sport-buddy.net\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"benkimball\",\n      \"name\": \"Ben Kimball\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/40365?v=4\",\n      \"profile\": \"http://iam.benkimball.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"liangjiapei\",\n      \"name\": \"Jiapei Liang\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/9281185?v=4\",\n      \"profile\": \"https://jiapei.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"RichardWright\",\n      \"name\": \"Richard W\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/881815?v=4\",\n      \"profile\": \"https://github.com/RichardWright\",\n      \"contributions\": [\n        \"ideas\",\n        \"research\"\n      ]\n    },\n    {\n      \"login\": \"leeweisberger\",\n      \"name\": \"Lee Weisberger\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6363419?v=4\",\n      \"profile\": \"https://github.com/leeweisberger\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"StarpTech\",\n      \"name\": \"Dustin Deus\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1764424?v=4\",\n      \"profile\": \"https://starptech.de/\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    }\n  ],\n  \"commitConvention\": \"none\"\n}\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: Node.js CI\n\non:\n  push:\n    branches: [master, main]\n  pull_request:\n    branches: [master, main]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [15.x, 16.x]\n\n    steps:\n      - uses: actions/checkout@v2\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v2\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: npm ci\n      - run: npm test\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  push:\n    branches:\n      - master\n      - main\njobs:\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - name: Setup Node.js\n        uses: actions/setup-node@v1\n        with:\n          node-version: 16\n      - name: Install dependencies\n        run: npm ci\n      - name: Release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n        run: npx semantic-release\n"
  },
  {
    "path": ".gitignore",
    "content": "dist/\nnode_modules/\n"
  },
  {
    "path": ".npmignore",
    "content": "*\n!src/**/*\n!dist/**/*\ndist/**/*.test.*\n!package.json\n!README.md"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2020 Daniel M. Schmidt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Apollo Opentracing [![npm version](https://badge.fury.io/js/apollo-opentracing.svg)](https://badge.fury.io/js/apollo-opentracing) [![Build Status](https://travis-ci.com/DanielMSchmidt/apollo-opentracing.svg?branch=master)](https://travis-ci.com/DanielMSchmidt/apollo-opentracing) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors)\n\nApollo Opentracing allows you to integrate open source baked performance tracing to your Apollo server based on industry standards for tracing.\n\n- 🚀 Request & Field level resolvers are traced out of the box\n- 🔍 Queries and results are logged, to make debugging easier\n- ⚙️ Select which requests you want to trace\n- 🔗 Spans transmitted through the HTTP Headers are picked up\n- 🔧 Use the opentracing compatible tracer you like, e.g.\n  - [jaeger](https://www.jaegertracing.io/)\n  - [zipkin](https://github.com/DanielMSchmidt/zipkin-javascript-opentracing)\n- 🦖 Support from node 6 on\n\n## Installation\n\nRun `npm install --save apollo-opentracing` given that you already setup an opentracing tracer accordingly.\n\n## Setup\n\nWe need two types of tracer (which could be identical if you like):\n\n- server: Only used for the root (the first span we will start)\n- local: Used to start every other span\n\n```diff\nconst { graphqlExpress } = require(\"apollo-server-express\");\nconst {serverTracer, localTracer} = require(\"./tracer\");\n+const OpentracingPlugin = require(\"apollo-opentracing\").default;\n\napp.use(\n  \"/graphql\",\n  bodyParser.json(),\n  graphqlExpress({\n    schema,\n+   plugins: [OpentracingPlugin({\n+     server: serverTracer,\n+     local: localTracer,\n+   })]\n  })\n)\n```\n\n## Connecting Services\n\n![example image](demo.png)\n\nTo connect other services you need to use the opentracing [inject](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) function of your tracer.\nWe pass the current span down to your resolvers as `info.span`, so you should use it.\n\nYou can also make use of it and add new logs or tags on the fly if you like.\nThis may look something like this:\n\n```js\nmyFieldResolver(source, args, context, info) {\n  const headers = {...};\n\n  const parentSpan = info.span;\n  // please use the same tracer you passed down to the extension\n  const networkSpan = tracer.startSpan(\"NetworkRequest:\" + endpoint, {\n    childOf: parentSpan\n  });\n\n  // Let's transfer the span information to the headers\n  tracer.inject(\n    networkSpan,\n    YourOpentracingImplementation.FORMAT_HTTP_HEADERS,\n    headers\n  );\n\n  return doNetworkRequest(endpoint, headers).then(result => {\n    networkSpan.finish()\n    return result;\n  }, err => {\n    networkSpan.log({\n      error: true,\n      errorMessage: err\n    });\n\n    networkSpan.finish();\n    return err;\n  });\n}\n```\n\n## Selective Tracing\n\nSometimes you don't want to trace everything, so we provide ways to select if you want to start a span right now or not.\n\n### By Request\n\nIf you construct the extension with `shouldTraceRequest` you get the option to opt-in or out on a request basis.\nWhen you don't start the span for the request the field resolvers will also not be used.\n\nThe function is called with the same arguments as the `requestDidStart` function extensions can provide, which is documented [here](https://github.com/apollographql/apollo-server/blob/master/packages/graphql-extensions/src/index.ts#L35).\n\nWhen the request is not traced there will also be no traces of the field resolvers.\n\n### By Field\n\nThere might be certain field resolvers that are not worth the tracing, e.g. when they get a value out of an object and need no further tracing. To control if you want a field resolver to be traced you can pass the `shouldTraceFieldResolver` option to the constructor. The function is called with the same arguments as your field resolver and you can get the name of the field by `info.fieldName`. When you return false no traces will be made of this field resolvers and all underlying ones.\n\n## Modifying span metadata\n\nIf you'd like to add custom tags or logs to span you can construct the extension with `onRequestResolve`. The function is called with two arguments: span and infos `onRequestResolve?: (span: Span, info: RequestStart)`\n\n## Using your own request span\n\nIf you need to take control of initializing the request span (e.g because you need to use it during context initialization) you can do so by having creating it as `context.requestSpan`.\n\n## Options\n\n- `server`: Opentracing Tracer for the incoming request\n- `local`: Opentracing Tracer for the local and outgoing requests\n- `onFieldResolve(source: any, args: { [argName: string]: any }, context: SpanContext, info: GraphQLResolveInfo)`: Allow users to add extra information to the span\n- `onFieldResolveFinish(error: Error | null, result: any, span: Span)`: Callback after a field was resolved\n- `shouldTraceRequest` & `shouldTraceFieldResolver`: See [Selective Tracing](#selective-tracing)\n- `onRequestResolve(span: Span, info: GraphQLRequestContext)`: Add extra information to the request span\n- `createCustomSpanName(name: String, info: GraphQLResolveInfo)`: Allow users to provide customized span name\n- `onRequestError(rootSpan: Span, info: GraphQLRequestContextDidEncounterErrors)`: Callback when a request errors\n\n## Contributing\n\nPlease feel free to add issues with new ideas, bugs and anything that might come up.\nLet's make performance measurement to everyone <3\n\n## Contributors\n\nThanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"http://danielmschmidt.de/\"><img src=\"https://avatars2.githubusercontent.com/u/1337046?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Daniel Schmidt</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=DanielMSchmidt\" title=\"Code\">💻</a> <a href=\"#ideas-DanielMSchmidt\" title=\"Ideas, Planning, & Feedback\">🤔</a></td>\n    <td align=\"center\"><a href=\"https://github.com/cliedeman\"><img src=\"https://avatars2.githubusercontent.com/u/3578740?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ciaran Liedeman</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/issues?q=author%3Acliedeman\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=cliedeman\" title=\"Code\">💻</a> <a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=cliedeman\" title=\"Tests\">⚠️</a></td>\n    <td align=\"center\"><a href=\"http://juhp.net\"><img src=\"https://avatars3.githubusercontent.com/u/453031?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jens Ulrich Hjuler Pedersen</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/issues?q=author%3AMultiply\" title=\"Bug reports\">🐛</a> <a href=\"#ideas-Multiply\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/pulls?q=is%3Apr+reviewed-by%3AMultiply\" title=\"Reviewed Pull Requests\">👀</a></td>\n    <td align=\"center\"><a href=\"https://github.com/frances3006\"><img src=\"https://avatars0.githubusercontent.com/u/9115596?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Francesca</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=frances3006\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://analogic.al\"><img src=\"https://avatars2.githubusercontent.com/u/84963?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ricardo Casares</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=ricardocasares\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://keybase.io/mwieczorek\"><img src=\"https://avatars2.githubusercontent.com/u/7051680?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Michał Wieczorek</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=mwieczorek\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://koen.pt\"><img src=\"https://avatars2.githubusercontent.com/u/351038?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koen Punt</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=koenpunt\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/zekenie\"><img src=\"https://avatars2.githubusercontent.com/u/962281?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Zeke Nierenberg</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=zekenie\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://app.sport-buddy.net\"><img src=\"https://avatars3.githubusercontent.com/u/1945040?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tomáš Voslař</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=voslartomas\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://iam.benkimball.com/\"><img src=\"https://avatars2.githubusercontent.com/u/40365?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ben Kimball</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=benkimball\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://jiapei.io/\"><img src=\"https://avatars.githubusercontent.com/u/9281185?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jiapei Liang</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=liangjiapei\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/RichardWright\"><img src=\"https://avatars.githubusercontent.com/u/881815?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Richard W</b></sub></a><br /><a href=\"#ideas-RichardWright\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#research-RichardWright\" title=\"Research\">🔬</a></td>\n    <td align=\"center\"><a href=\"https://github.com/leeweisberger\"><img src=\"https://avatars.githubusercontent.com/u/6363419?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Lee Weisberger</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/commits?author=leeweisberger\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://starptech.de/\"><img src=\"https://avatars.githubusercontent.com/u/1764424?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Dustin Deus</b></sub></a><br /><a href=\"https://github.com/DanielMSchmidt/apollo-opentracing/issues?q=author%3AStarpTech\" title=\"Bug reports\">🐛</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-restore -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!\n\n## License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = {\n  extends: [\"@commitlint/config-conventional\"],\n  rules: {\n    \"scope-case\": [0],\n  },\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"apollo-opentracing\",\n  \"version\": \"0.0.0-development\",\n  \"description\": \"Trace your GraphQL server with Opentracing\",\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"scripts\": {\n    \"clean\": \"rm -rf dist\",\n    \"compile\": \"tsc\",\n    \"prepare\": \"npm run clean && npm run compile\",\n    \"test\": \"jest\",\n    \"semantic-release\": \"semantic-release\",\n    \"travis-deploy-once\": \"travis-deploy-once --pro\",\n    \"contributors:add\": \"all-contributors add\",\n    \"contributors:generate\": \"all-contributors generate\"\n  },\n  \"jest\": {\n    \"transform\": {\n      \"^.+\\\\.tsx?$\": \"ts-jest\"\n    },\n    \"testRegex\": \"(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.(jsx?|tsx?)$\",\n    \"moduleFileExtensions\": [\n      \"ts\",\n      \"tsx\",\n      \"js\",\n      \"jsx\",\n      \"json\",\n      \"node\"\n    ]\n  },\n  \"repository\": \"DanielMSchmidt/apollo-opentracing\",\n  \"author\": \"Daniel Schmidt <danielmschmidt92@gmail.com>\",\n  \"license\": \"MIT\",\n  \"peerDependencies\": {\n    \"apollo-server\": \">=3.0.0\",\n    \"apollo-server-env\": \"*\",\n    \"graphql\": \">=0.10.x\",\n    \"opentracing\": \"*\"\n  },\n  \"dependencies\": {\n    \"apollo-server-plugin-base\": \"^3.0.0\",\n    \"apollo-server-types\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/config-conventional\": \"11.0.0\",\n    \"@commitlint/travis-cli\": \"17.8.1\",\n    \"@types/jest\": \"26.0.24\",\n    \"@types/node\": \"14.18.63\",\n    \"@types/supertest\": \"2.0.16\",\n    \"all-contributors-cli\": \"6.26.1\",\n    \"apollo-server\": \"3.13.0\",\n    \"apollo-server-env\": \"4.2.1\",\n    \"express\": \"4.22.1\",\n    \"graphql\": \"15.10.1\",\n    \"graphql-tools\": \"6.2.6\",\n    \"jest\": \"27.5.1\",\n    \"opentracing\": \"0.14.7\",\n    \"semantic-release\": \"20.1.3\",\n    \"supertest\": \"5.0.0\",\n    \"travis-deploy-once\": \"5.0.11\",\n    \"ts-jest\": \"27.0.4\",\n    \"typescript\": \"4.9.5\"\n  }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\"config:base\", \":semanticCommits\"],\n  \"automerge\": true,\n  \"commitMessagePrefix\": \"fix:\",\n  \"major\": {\n    \"automerge\": false\n  }\n}\n"
  },
  {
    "path": "src/__tests__/__snapshots__/integration-test.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`integration with apollo-server alias with fragment works 1`] = `\nrequest:1\n   finished: true\n\n+-- a:2\n      finished: true\n\n   +-- dos:3\n         finished: true\n\n\n`;\n\nexports[`integration with apollo-server alias works 1`] = `\nrequest:1\n   finished: true\n\n+-- a:2\n      finished: true\n\n   +-- uno:3\n         finished: true\n\n   +-- two:4\n         finished: true\n\n\n`;\n\nexports[`integration with apollo-server correct span nesting 1`] = `\nrequest:1\n   finished: true\n\n+-- a:2\n      finished: true\n\n   +-- one:3\n         finished: true\n\n   +-- two:4\n         finished: true\n\n\n`;\n\nexports[`integration with apollo-server does not start a field resolver span if the parent field resolver was not traced 1`] = `\nrequest:1\n   finished: true\n\n+-- b:2\n      finished: true\n\n   +-- four:3\n         finished: true\n\n\n`;\n\nexports[`integration with apollo-server implements traces for arrays 1`] = `\nrequest:1\n   finished: true\n\n+-- as:2\n      finished: true\n\n   +-- one:3\n         finished: true\n\n   +-- two:4\n         finished: true\n\n   +-- one:5\n         finished: true\n\n   +-- two:6\n         finished: true\n\n   +-- one:7\n         finished: true\n\n   +-- two:8\n         finished: true\n\n\n`;\n\nexports[`integration with apollo-server onFieldResolve & onFieldResolveFinish 1`] = `\nrequest:1\n   finished: true\n\n+-- a:2\n      finished: true\n      logs:\n      1. {\"onFieldResolve\":\"yes\"}\n      2. {\"onFieldResolveFinish\":\"yes\"}\n\n   +-- one:3\n         finished: true\n         logs:\n         1. {\"onFieldResolve\":\"yes\"}\n         2. {\"onFieldResolveFinish\":\"yes\"}\n\n   +-- two:4\n         finished: true\n         logs:\n         1. {\"onFieldResolve\":\"yes\"}\n         2. {\"onFieldResolveFinish\":\"yes\"}\n\n+-- b:5\n      finished: true\n      logs:\n      1. {\"onFieldResolve\":\"yes\"}\n      2. {\"onFieldResolveFinish\":\"yes\"}\n\n   +-- four:6\n         finished: true\n         logs:\n         1. {\"onFieldResolve\":\"yes\"}\n         2. {\"onFieldResolveFinish\":\"yes\"}\n\n\n`;\n\nexports[`integration with apollo-server onRequestError 1`] = `\nrequest:1\n   finished: true\n   logs:\n   1. {\"onRequestError\":\"yes\"}\n\n+-- e:2\n      finished: true\n\n\n`;\n\nexports[`integration with apollo-server onRequestResolve 1`] = `\nrequest:1\n   finished: true\n   logs:\n   1. {\"onRequestResolve\":\"yes\"}\n\n+-- a:2\n      finished: true\n\n   +-- one:3\n         finished: true\n\n   +-- two:4\n         finished: true\n\n+-- b:5\n      finished: true\n\n   +-- four:6\n         finished: true\n\n\n`;\n\nexports[`integration with apollo-server picks up external spans 1`] = `\nexternal:-1\n   finished: false\n\n+-- request:1\n      finished: true\n\n   +-- a:2\n         finished: true\n\n      +-- one:3\n            finished: true\n\n\n`;\n"
  },
  {
    "path": "src/__tests__/integration-test.ts",
    "content": "import * as express from \"express\";\nimport * as request from \"supertest\";\nimport { ApolloServer } from \"apollo-server-express\";\nimport { Tracer } from \"opentracing\";\nimport { MockSpan, MockSpanTree } from \"../test/types\";\nimport spanSerializer from \"../test/span-serializer\";\nimport ApolloOpentracing, { InitOptions, SpanContext } from \"../\";\n\nexpect.addSnapshotSerializer(spanSerializer);\n\nlet mockSpanId = 1;\n\n// Stable spanId's\nbeforeEach(() => {\n  mockSpanId = 1;\n});\n\nconst buildSpanTree = (spans: MockSpan[]) => {\n  // TODO we currently assume there is only one null parent entry.\n  const rootSpan = spans.find((span) => !span.parentId);\n  if (!rootSpan) {\n    throw new Error(\"No root span found\");\n  }\n  const spansByParentId = spans.reduce((acc, span) => {\n    // Check for root\n    if (span.parentId) {\n      if (acc.has(span.parentId)) {\n        acc.get(span.parentId)?.push(span);\n      } else {\n        acc.set(span.parentId, [span]);\n      }\n    }\n\n    return acc;\n  }, new Map<number, MockSpan[]>());\n\n  expect(rootSpan).toBeDefined();\n\n  const tree: MockSpanTree = {\n    ...rootSpan,\n    children: [],\n  };\n\n  buildTree(tree, spansByParentId);\n\n  // Lost Spans\n  expect(spansByParentId.size).toBe(0);\n\n  return tree;\n};\n\nconst buildTree = (\n  parent: MockSpanTree,\n  spansByParentId: Map<number, MockSpan[]>\n) => {\n  if (spansByParentId.has(parent.id)) {\n    const spans = spansByParentId.get(parent.id);\n    if (!spans) {\n      throw new Error(\n        \"Could not find the spans for parent \" +\n          parent.name +\n          \" with id \" +\n          parent.id\n      );\n    }\n    spansByParentId.delete(parent.id);\n\n    // TODO: do we need to sort?\n    for (const span of spans) {\n      const node = {\n        ...span,\n        children: [],\n      };\n\n      parent.children.push(node);\n      buildTree(node, spansByParentId);\n    }\n  }\n};\n\nfunction buildEmptySpan(\n  id: number,\n  name: string,\n  parentId?: number,\n  options?: any\n) {\n  return {\n    id,\n    parentId,\n    name,\n    options,\n    logs: [],\n    tags: [],\n    finished: false,\n  };\n}\n\nclass MockTracer {\n  spans: MockSpan[];\n  constructor() {\n    this.spans = [];\n  }\n\n  extract(_idk: any, header: Record<string, string>) {\n    // we use this as a name and -1 as id\n    const externalSpanId = header[\"x-b3-spanid\"];\n    if (!externalSpanId) {\n      return null;\n    }\n\n    const externalSpan = buildEmptySpan(-1, externalSpanId);\n    this.spans.push(externalSpan);\n    return externalSpan;\n  }\n\n  startSpan(name: string, options: any) {\n    const spanId = mockSpanId++;\n\n    this.spans.push(\n      buildEmptySpan(spanId, name, options?.childOf?.id, options)\n    );\n\n    const self = this;\n\n    return {\n      log(object: any) {\n        self.spans.find((span) => span.id === spanId)?.logs.push(object);\n      },\n\n      setTag(key: string, value: any) {\n        self.spans\n          .find((span) => span.id === spanId)\n          ?.tags.push({ key: key, value: value });\n      },\n\n      id: spanId,\n      // Added for debugging\n      name,\n\n      finish() {\n        const span = self.spans.find((span) => span.id === spanId);\n        if (span) {\n          span.finished = true;\n        }\n      },\n    };\n  }\n}\n\nasync function createApp<InstanceContext extends SpanContext>({\n  tracer,\n  ...params\n}: { tracer: MockTracer } & Omit<\n  InitOptions<InstanceContext>,\n  \"server\" | \"local\"\n>) {\n  const app = express();\n\n  const server = new ApolloServer({\n    typeDefs: `\n      type A {\n        one: String\n        two: String\n        three: [B]\n      }\n\n      type B {\n        four: String  \n      }\n\n      type Query {\n        a: A\n        b: B\n        e: B\n        as: [A]\n        bs: [B]\n      }\n    `,\n    resolvers: {\n      Query: {\n        a() {\n          return {\n            one: \"1\",\n            two: \"2\",\n            three: [{ four: \"4\" }, { four: \"IV\" }],\n          };\n        },\n        b() {\n          return {\n            four: \"4\",\n          };\n        },\n        e() {\n          return new Error(\"error!\");\n        },\n\n        as() {\n          return [\n            {\n              one: \"1\",\n              two: \"2\",\n            },\n            {\n              one: \"I\",\n              two: \"II\",\n            },\n            {\n              one: \"eins\",\n              two: \"zwei\",\n            },\n          ];\n        },\n      },\n    },\n    plugins: [\n      ApolloOpentracing({\n        ...params,\n        server: tracer as unknown as Tracer,\n        local: tracer as unknown as Tracer,\n      }),\n    ],\n  });\n  await server.start();\n  server.applyMiddleware({ app });\n\n  return app;\n}\n\ndescribe(\"integration with apollo-server\", () => {\n  it(\"closes all spans\", async () => {\n    const tracer = new MockTracer();\n    const app = await createApp({ tracer });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n        a {\n          one\n        }\n      }`,\n      })\n      .expect(200);\n\n    expect(tracer.spans.length).toBe(3);\n    expect(tracer.spans.filter((span) => span.finished).length).toBe(3);\n  });\n\n  it(\"correct span nesting\", async () => {\n    const tracer = new MockTracer();\n    const app = await createApp({ tracer });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n        a {\n          one\n          two\n        }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n  });\n\n  it(\"does not start a field resolver span if the parent field resolver was not traced\", async () => {\n    const tracer = new MockTracer();\n    const shouldTraceFieldResolver = (\n      _source: any,\n      _args: any,\n      _ctx: any,\n      info: any\n    ) => {\n      if (info.fieldName === \"a\") {\n        return false;\n      }\n      return true;\n    };\n\n    const app = await createApp({ tracer, shouldTraceFieldResolver });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n        a {\n          one\n          two\n        }\n        b {\n          four\n        }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n  });\n\n  it(\"implements traces for arrays\", async () => {\n    const tracer = new MockTracer();\n    const app = await createApp({ tracer });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n        as {\n          one\n          two\n        }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n  });\n\n  it(\"alias works\", async () => {\n    const tracer = new MockTracer();\n    const app = await createApp({ tracer });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n        a {\n          uno: one\n          two\n        }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n  });\n\n  it(\"alias with fragment works\", async () => {\n    const tracer = new MockTracer();\n    const app = await createApp({ tracer });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `\n        fragment F on A {\n          dos: two\n        }\n\n        query {\n        a {\n          ...F\n        }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n  });\n\n  it(\"onFieldResolve & onFieldResolveFinish\", async () => {\n    const tracer = new MockTracer();\n    const onFieldResolve = jest.fn(\n      (_s: any, _args: any, _context: any, info: any) => {\n        info.span.log({ onFieldResolve: \"yes\" });\n      }\n    );\n    const onFieldResolveFinish = jest.fn(\n      (_err: any, _result: any, span: any) => {\n        span.log({ onFieldResolveFinish: \"yes\" });\n      }\n    );\n\n    const app = await createApp({\n      tracer,\n      onFieldResolve,\n      onFieldResolveFinish,\n    });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n          a {\n            one\n            two\n          }\n          b {\n            four\n          }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n    expect(onFieldResolve).toHaveBeenCalledTimes(5);\n    expect(onFieldResolveFinish).toHaveBeenCalledTimes(5);\n  });\n\n  it(\"shouldTraceRequest disables tracing\", async () => {\n    const tracer = new MockTracer();\n    const shouldTraceRequest = jest.fn(() => false);\n    const app = await createApp({ tracer, shouldTraceRequest });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n          a {\n            one\n          }\n      }`,\n      })\n      .expect(200);\n\n    expect(shouldTraceRequest).toHaveBeenCalledTimes(1);\n    expect(tracer.spans.length).toBe(0);\n  });\n\n  it(\"onRequestResolve\", async () => {\n    const tracer = new MockTracer();\n    const onRequestResolve = jest.fn((span: any) => {\n      span.log({ onRequestResolve: \"yes\" });\n    });\n\n    const app = await createApp({ tracer, onRequestResolve });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n          a {\n            one\n            two\n          }\n          b {\n            four\n          }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n    expect(onRequestResolve).toHaveBeenCalledTimes(1);\n  });\n\n  it(\"onRequestError\", async () => {\n    const tracer = new MockTracer();\n    const onRequestError = jest.fn((span: any) => {\n      span.log({ onRequestError: \"yes\" });\n    });\n\n    const app = await createApp({ tracer, onRequestError });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .send({\n        query: `query {\n          e {\n            four\n          }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n    expect(onRequestError).toHaveBeenCalledTimes(1);\n  });\n\n  it(\"picks up external spans\", async () => {\n    const tracer = new MockTracer();\n\n    const app = await createApp({ tracer });\n    await request(app)\n      .post(\"/graphql\")\n      .set(\"Accept\", \"application/json\")\n      .set(\"x-b3-traceid\", \"external\")\n      .set(\"x-b3-spanid\", \"external\")\n      .send({\n        query: `query {\n          a {\n            one\n          }\n      }`,\n      })\n      .expect(200);\n\n    const tree = buildSpanTree(tracer.spans);\n    expect(tree).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/context.ts",
    "content": "import { Span } from \"opentracing\";\nimport { GraphQLResolveInfo, ResponsePath } from \"graphql\";\nimport { GraphQLRequestContext } from \"apollo-server-plugin-base\";\nfunction isArrayPath(path: ResponsePath) {\n  return typeof path.key === \"number\";\n}\n\nexport function buildPath(path: ResponsePath | undefined) {\n  let current = path;\n  const segments = [];\n  while (current != null) {\n    if (isArrayPath(current)) {\n      segments.push(`[${current.key}]`);\n    } else {\n      segments.push(current.key);\n    }\n    current = current.prev;\n  }\n  return segments.reverse().join(\".\");\n}\n\nexport interface SpanContext extends Object {\n  _spans: Map<string, Span>;\n  getSpanByPath(info: ResponsePath): Span | undefined;\n  addSpan(span: Span, info: GraphQLResolveInfo): void;\n  // Passed in from the outside context\n  requestSpan?: Span;\n}\n\nfunction isSpanContext(obj: any): obj is SpanContext {\n  return (\n    obj.getSpanByPath instanceof Function && obj.addSpan instanceof Function\n  );\n}\n\nexport function requestIsInstanceContextRequest<CTX extends SpanContext>(\n  request: GraphQLRequestContext<CTX | Object>\n): request is GraphQLRequestContext<CTX> {\n  return isSpanContext(request.context);\n}\n\n// TODO: think about using symbols to hide these\nexport function addContextHelpers(obj: any): SpanContext {\n  if (isSpanContext(obj)) {\n    return obj;\n  }\n\n  obj._spans = new Map<string, Span>();\n  obj.getSpanByPath = function (path: ResponsePath): Span | undefined {\n    return this._spans.get(buildPath(isArrayPath(path) ? path.prev : path));\n  };\n\n  obj.addSpan = function (span: Span, info: GraphQLResolveInfo): void {\n    this._spans.set(buildPath(info.path), span);\n  };\n\n  return obj;\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import { Request } from \"apollo-server-env\";\nimport {\n  ApolloServerPlugin,\n  GraphQLRequestContext,\n  GraphQLRequestListener,\n  GraphQLRequestExecutionListener,\n  GraphQLFieldResolverParams,\n  GraphQLRequestContextDidEncounterErrors,\n} from \"apollo-server-plugin-base\";\nimport { DocumentNode, GraphQLResolveInfo } from \"graphql\";\nimport { FORMAT_HTTP_HEADERS, Span, Tracer } from \"opentracing\";\nimport {\n  addContextHelpers,\n  SpanContext,\n  requestIsInstanceContextRequest,\n} from \"./context\";\n\nexport { SpanContext, addContextHelpers };\n\nconst alwaysTrue = () => true;\nconst emptyFunction = () => {};\n\nexport interface InitOptions<TContext> {\n  server?: Tracer;\n  local?: Tracer;\n  onFieldResolveFinish?: (error: Error | null, result: any, span: Span) => void;\n  onFieldResolve?: (\n    source: any,\n    args: { [argName: string]: any },\n    context: SpanContext,\n    info: GraphQLResolveInfo\n  ) => void;\n  shouldTraceRequest?: (info: GraphQLRequestContext<TContext>) => boolean;\n  shouldTraceFieldResolver?: (\n    source: any,\n    args: { [argName: string]: any },\n    context: SpanContext,\n    info: GraphQLResolveInfo\n  ) => boolean;\n  onRequestResolve?: (\n    span: Span,\n    info: GraphQLRequestContext<TContext>\n  ) => void;\n  createCustomSpanName?: (name: string, info: GraphQLResolveInfo) => string;\n  onRequestError?: (\n    rootSpan: Span,\n    info: GraphQLRequestContextDidEncounterErrors<TContext>\n  ) => void;\n}\n\nexport interface ExtendedGraphQLResolveInfo extends GraphQLResolveInfo {\n  span?: Span;\n}\nexport interface RequestStart<TContext> {\n  request: Pick<Request, \"url\" | \"method\" | \"headers\">;\n  queryString?: string;\n  parsedQuery?: DocumentNode;\n  operationName?: string;\n  variables?: { [key: string]: any };\n  persistedQueryHit?: boolean;\n  persistedQueryRegister?: boolean;\n  context: TContext;\n  requestContext: GraphQLRequestContext<TContext>;\n}\n\nfunction getFieldName(info: GraphQLResolveInfo) {\n  if (\n    info.fieldNodes &&\n    info.fieldNodes.length > 0 &&\n    info.fieldNodes[0].alias\n  ) {\n    return info.fieldNodes[0].alias.value;\n  }\n\n  return info.fieldName || \"field\";\n}\n\nfunction headersToOject(\n  headerIterator: Iterator<[string, string], any, undefined> | undefined\n): Record<string, string> {\n  if (!headerIterator) {\n    return {};\n  }\n\n  const headers: Record<string, string> = {};\n  let header:\n    | IteratorYieldResult<[string, string]>\n    | IteratorReturnResult<any>\n    | undefined;\n  do {\n    header = headerIterator?.next();\n    if (header?.value) {\n      const [key, value] = header?.value;\n      headers[key] = value;\n    }\n  } while (!header?.done);\n\n  return headers;\n}\n\nexport default function OpentracingPlugin<InstanceContext extends SpanContext>({\n  server,\n  local,\n  onFieldResolveFinish = emptyFunction,\n  onFieldResolve = emptyFunction,\n  shouldTraceRequest = alwaysTrue,\n  shouldTraceFieldResolver = alwaysTrue,\n  onRequestResolve = emptyFunction,\n  onRequestError = emptyFunction,\n  createCustomSpanName = (name, _) => name,\n}: InitOptions<InstanceContext>): ApolloServerPlugin<InstanceContext> {\n  if (!server) {\n    throw new Error(\n      \"ApolloOpentracing needs a server tracer, please provide it to the constructor. e.g. new ApolloOpentracing({ server: serverTracer, local: localTracer })\"\n    );\n  }\n\n  if (!local) {\n    throw new Error(\n      \"ApolloOpentracing needs a local tracer, please provide it to the constructor. e.g. new ApolloOpentracing({ server: serverTracer, local: localTracer })\"\n    );\n  }\n  const serverTracer = server;\n  const localTracer = local;\n\n  let requestSpan: Span | null = null;\n  return {\n    async requestDidStart(\n      infos: GraphQLRequestContext<InstanceContext>\n    ): Promise<GraphQLRequestListener<InstanceContext> | void> {\n      addContextHelpers(infos.context);\n      if (!requestIsInstanceContextRequest<InstanceContext>(infos)) {\n        console.warn(\n          \"Context in request passed to apollo-opentracing#requestDidStart is not a SpanContext, aborting tracing\"\n        );\n        return;\n      }\n      if (!shouldTraceRequest(infos)) {\n        return;\n      }\n\n      const headers = headersToOject(infos.request.http?.headers.entries());\n\n      const externalSpan = headers\n        ? serverTracer.extract(FORMAT_HTTP_HEADERS, headers)\n        : undefined;\n\n      const rootSpan =\n        infos.context.requestSpan ||\n        serverTracer.startSpan(infos.operationName || \"request\", {\n          childOf: externalSpan ? externalSpan : undefined,\n        });\n\n      onRequestResolve(rootSpan, infos);\n      requestSpan = rootSpan;\n\n      return {\n        async willSendResponse() {\n          rootSpan.finish();\n        },\n\n        async executionDidStart(): Promise<\n          GraphQLRequestExecutionListener<InstanceContext>\n        > {\n          return {\n            willResolveField({\n              source,\n              args,\n              context,\n              info,\n            }: GraphQLFieldResolverParams<any, InstanceContext>) {\n              if (\n                // we don't trace the request\n                !requestSpan ||\n                // we should not trace this resolver\n                !shouldTraceFieldResolver(source, args, context, info) ||\n                // the previous resolver was not traced\n                (info.path &&\n                  info.path.prev &&\n                  !context.getSpanByPath(info.path.prev))\n              ) {\n                return;\n              }\n\n              // idempotent method to add helpers to the first context available (which will be propagated by apollo)\n              addContextHelpers(context);\n\n              const name = createCustomSpanName(getFieldName(info), info);\n              const parentSpan =\n                info.path && info.path.prev\n                  ? context.getSpanByPath(info.path.prev)\n                  : requestSpan;\n\n              const span = localTracer.startSpan(name, {\n                childOf: parentSpan || undefined,\n              });\n\n              context.addSpan(span, info);\n              // expose to field (although type does not contain it)\n              (info as any).span = span;\n\n              onFieldResolve(source, args, context, info);\n\n              return (error: Error | null, result: any) => {\n                onFieldResolveFinish(error, result, span);\n\n                span.finish();\n              };\n            },\n          };\n        },\n\n        didEncounterErrors: async (requestContext) => {\n          onRequestError(rootSpan, requestContext);\n        },\n      };\n    },\n  };\n}\n"
  },
  {
    "path": "src/test/span-serializer.ts",
    "content": "import \"jest\";\nimport { MockSpanTree } from \"./types\";\n\nconst TAB = \"   \";\n\nfunction prefix(depth: number) {\n  return TAB.repeat(depth) + `+-- `;\n}\n\nfunction logLine(log: any, index: number, depth: number) {\n  return `${TAB.repeat(depth + 1)}${index}. ${JSON.stringify(log).replace(\n    /\\\\\"/g,\n    \"\"\n  )}`;\n}\n\nfunction logs(span: MockSpanTree, depth: number) {\n  if (span.logs && span.logs.length > 0) {\n    return `${TAB.repeat(depth + 1)}logs:\\n${span.logs\n      .map((log, index) => logLine(log, index + 1, depth))\n      .join(\"\\n\")}\\n`;\n  }\n\n  return \"\";\n}\n\nfunction tags(span: MockSpanTree, depth: number) {\n  if (span.tags && span.tags.length > 0) {\n    return `${TAB.repeat(depth + 1)}tags:\\n${span.tags\n      .map((tag, index) => logLine(tag, index + 1, depth))\n      .join(\"\\n\")}\\n`;\n  }\n  return \"\";\n}\n\nfunction tag(span: MockSpanTree, depth: number) {\n  return `${span.name}:${span.id}\\n${TAB.repeat(depth + 1)}finished: ${\n    span.finished\n  }\\n${logs(span, depth)}\\n${tags(span, depth)}`;\n}\n\nfunction buildSpan(span: MockSpanTree, depth = 0) {\n  let result = \"\";\n\n  result += tag(span, depth);\n\n  if (span.children) {\n    for (const child of span.children) {\n      result += `${prefix(depth)}${buildSpan(child, depth + 1)}`;\n    }\n  }\n\n  return result;\n}\n\nexport default {\n  test: (val: any) => val.id && val.name && val.logs && val.finished != null,\n  print(val: any) {\n    return buildSpan(val as MockSpanTree);\n  },\n};\n"
  },
  {
    "path": "src/test/types.ts",
    "content": "export interface MockSpan {\n  id: number;\n  parentId?: number;\n  name: string;\n  options?: any;\n  logs: any[];\n  tags: any[];\n  finished: boolean;\n}\n\nexport interface MockSpanTree extends MockSpan {\n  children: MockSpanTree[];\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2016\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUnusedParameters\": true,\n    \"noUnusedLocals\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"lib\": [\"es2017\", \"esnext.asynciterable\"],\n    \"rootDir\": \"./src\",\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"**/__tests__\", \"**/__mocks__\"]\n}\n"
  }
]