├── .npm └── package │ ├── .gitignore │ ├── README │ └── npm-shrinkwrap.json ├── .prettierrc ├── assets ├── index-pattern.png ├── meteor-call-2.png ├── meteor-call.png ├── sessions_count.png ├── incoming-requests.png ├── meteor-publication.png └── meteor-collection-details.png ├── .babelrc ├── .travis.yml ├── jest.config.js ├── constants.js ├── .editorconfig ├── __tests__ ├── mocks │ ├── fibers.js │ ├── session.js │ ├── subscription.js │ ├── mongoCursor.js │ ├── agent.js │ └── meteor.js ├── asyncInstrument.test.js ├── subscriptionInstrument.test.js ├── methodsInstrument.js ├── sessionInstrument.test.js └── dbInstrument.test.js ├── .eslintrc ├── meteor-elastic-apm-tests.js ├── metrics.js ├── instrumenting ├── async.js ├── http-in.js ├── http-out.js ├── subscription.js ├── methods.js ├── session.js └── db.js ├── LICENSE ├── .gitignore ├── package.js ├── hacks.js ├── package.json ├── .github └── workflows │ └── comment-issue.yml ├── meteor-elastic-apm.js ├── METRICS.md ├── meteorx.js └── README.md /.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /assets/index-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-elastic-apm/HEAD/assets/index-pattern.png -------------------------------------------------------------------------------- /assets/meteor-call-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-elastic-apm/HEAD/assets/meteor-call-2.png -------------------------------------------------------------------------------- /assets/meteor-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-elastic-apm/HEAD/assets/meteor-call.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["transform-es2015-modules-commonjs"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/sessions_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-elastic-apm/HEAD/assets/sessions_count.png -------------------------------------------------------------------------------- /assets/incoming-requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-elastic-apm/HEAD/assets/incoming-requests.png -------------------------------------------------------------------------------- /assets/meteor-publication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-elastic-apm/HEAD/assets/meteor-publication.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | 5 | script: 6 | - npm run lint 7 | - npm test 8 | - npm run report-coverage 9 | -------------------------------------------------------------------------------- /assets/meteor-collection-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-elastic-apm/HEAD/assets/meteor-collection-details.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.(js|jsx)?$': 'babel-jest' 4 | }, 5 | roots: [''], 6 | modulePathIgnorePatterns: ['/__tests__/mocks'] 7 | }; 8 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ASYNC: 'async', 3 | DB: 'db', 4 | HTTP: 'http', 5 | HTTP_OUTGOING: 'http.outgoing', 6 | HTTP_INCOMING: 'http.incoming', 7 | EXECUTION: 'execution' 8 | }; 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | 9 | [*.js] 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /__tests__/mocks/fibers.js: -------------------------------------------------------------------------------- 1 | function newFibers() { 2 | function Fibers() {} 3 | 4 | const fibersInstance = new Fibers(); 5 | 6 | Fibers.yield = jest.fn; 7 | Fibers.current = fibersInstance; 8 | Fibers.prototype.run = jest.fn(); 9 | 10 | return Fibers; 11 | } 12 | 13 | module.exports = newFibers; 14 | -------------------------------------------------------------------------------- /__tests__/mocks/session.js: -------------------------------------------------------------------------------- 1 | function newSession() { 2 | function Session() {} 3 | 4 | Session.prototype.processMessage = jest.fn(); 5 | Session.prototype.protocol_handlers = { 6 | method: jest.fn(), 7 | sub: jest.fn(), 8 | unsub: jest.fn() 9 | }; 10 | 11 | return Session; 12 | } 13 | 14 | module.exports = newSession; 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": ["error"], 6 | "class-methods-use-this": [0], 7 | "prefer-arrow-callback": "off", 8 | "func-names": "off", 9 | "no-underscore-dangle": "off", 10 | "no-param-reassign": "off" 11 | }, 12 | "env": { 13 | "commonjs": true, 14 | "node": true, 15 | "jest": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /meteor-elastic-apm-tests.js: -------------------------------------------------------------------------------- 1 | // // Import Tinytest from the tinytest Meteor package. 2 | // import { Tinytest } from 'meteor/tinytest'; 3 | 4 | // // Import and rename a variable exported by meteor-elastic-apm.js. 5 | // import { name as packageName } from 'meteor/kschingiz:meteor-elastic-apm'; 6 | 7 | // // Write your tests here! 8 | // // Here is an example. 9 | // Tinytest.add('meteor-elastic-apm - example', function(test) { 10 | // test.equal(packageName, 'meteor-elastic-apm'); 11 | // }); 12 | -------------------------------------------------------------------------------- /__tests__/mocks/subscription.js: -------------------------------------------------------------------------------- 1 | function newSubscription() { 2 | function Subscription() {} 3 | 4 | Subscription.prototype._runHandler = jest.fn(); 5 | Subscription.prototype.ready = jest.fn(); 6 | Subscription.prototype.error = jest.fn(); 7 | 8 | Subscription.prototype.added = jest.fn(); 9 | Subscription.prototype.changed = jest.fn(); 10 | Subscription.prototype.removed = jest.fn(); 11 | Subscription.prototype._documents = { 12 | testCollection: {} 13 | }; 14 | 15 | return Subscription; 16 | } 17 | 18 | module.exports = newSubscription; 19 | -------------------------------------------------------------------------------- /__tests__/mocks/mongoCursor.js: -------------------------------------------------------------------------------- 1 | function newMongoCursor() { 2 | function MongoCursor() {} 3 | 4 | MongoCursor.prototype._name = 'test collection'; 5 | MongoCursor.prototype.forEach = jest.fn(); 6 | MongoCursor.prototype.map = jest.fn(); 7 | MongoCursor.prototype.fetch = jest.fn(() => [1, 2, 3, 4]); 8 | MongoCursor.prototype.count = jest.fn(); 9 | MongoCursor.prototype.observeChanges = jest.fn(); 10 | MongoCursor.prototype.observe = jest.fn(); 11 | MongoCursor.prototype.rewind = jest.fn(); 12 | 13 | return MongoCursor; 14 | } 15 | 16 | module.exports = newMongoCursor; 17 | -------------------------------------------------------------------------------- /__tests__/mocks/agent.js: -------------------------------------------------------------------------------- 1 | const agent = require('elastic-apm-node'); 2 | 3 | jest.mock('elastic-apm-node'); 4 | 5 | function newAgent() { 6 | agent.currentTransaction = undefined; 7 | agent.currentSpan = undefined; 8 | agent.currentTraceparent = undefined; 9 | 10 | agent.captureError = jest.fn(); 11 | agent.startSpan = jest.fn(() => ({ 12 | end: jest.fn(), 13 | addLabels: jest.fn() 14 | })); 15 | agent.startTransaction = jest.fn(() => ({ 16 | addLabels: jest.fn(), 17 | end: jest.fn() 18 | })); 19 | 20 | return agent; 21 | } 22 | 23 | module.exports = newAgent; 24 | -------------------------------------------------------------------------------- /__tests__/mocks/meteor.js: -------------------------------------------------------------------------------- 1 | function newMeteor() { 2 | function Collection() {} 3 | 4 | Collection.prototype.findOne = jest.fn(); 5 | Collection.prototype.find = jest.fn(); 6 | Collection.prototype.update = jest.fn(); 7 | Collection.prototype.remove = jest.fn(); 8 | Collection.prototype.insert = jest.fn(); 9 | Collection.prototype.createIndex = jest.fn(); 10 | Collection.prototype._dropIndex = jest.fn(); 11 | 12 | return { 13 | call: jest.fn(), 14 | subscribe: jest.fn(), 15 | publish: jest.fn(), 16 | Collection, 17 | methods(methodsMap) { 18 | this.server.method_handlers = { 19 | ...this.server.method_handlers, 20 | ...methodsMap 21 | }; 22 | }, 23 | server: { 24 | method_handlers: {} 25 | } 26 | }; 27 | } 28 | 29 | module.exports = newMeteor; 30 | -------------------------------------------------------------------------------- /metrics.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable import/no-unresolved */ 3 | import meteorMeasured from 'meteor/kschingiz:meteor-measured'; 4 | 5 | function startMetrics(agent) { 6 | const metrics = agent._metrics || {}; 7 | 8 | // ugly hack to find metrics registry 9 | const registrySymbol = Object.getOwnPropertySymbols(metrics).find( 10 | symbol => symbol.toString() === 'Symbol(metrics-registry)' 11 | ); 12 | 13 | const registry = metrics[registrySymbol]; 14 | 15 | if (registry) { 16 | try { 17 | meteorMeasured(registry); 18 | agent.logger.debug('Successfully started meteor-measured'); 19 | } catch (e) { 20 | agent.logger.error('Metrics could not be started'); 21 | throw e; 22 | } 23 | } else { 24 | agent.logger.error('Metrics could not be started, agent registry not found'); 25 | } 26 | } 27 | 28 | module.exports = startMetrics; 29 | -------------------------------------------------------------------------------- /instrumenting/async.js: -------------------------------------------------------------------------------- 1 | import shimmer from 'shimmer'; 2 | import { ASYNC, DB } from '../constants'; 3 | 4 | const EventSymbol = Symbol('ASYNC'); 5 | 6 | function start(agent, Fibers) { 7 | shimmer.wrap(Fibers, 'yield', function(original) { 8 | return function(...args) { 9 | const parentSpan = agent.currentSpan || {}; 10 | 11 | const validParents = parentSpan.type !== ASYNC && parentSpan.type !== DB; 12 | 13 | if (validParents) { 14 | Fibers.current[EventSymbol] = agent.startSpan(ASYNC, ASYNC); 15 | } 16 | 17 | return original.apply(this, args); 18 | }; 19 | }); 20 | 21 | shimmer.wrap(Fibers.prototype, 'run', function(original) { 22 | return function(...args) { 23 | if (this[EventSymbol]) { 24 | this[EventSymbol].end(); 25 | this[EventSymbol] = undefined; 26 | } 27 | return original.apply(this, args); 28 | }; 29 | }); 30 | } 31 | 32 | module.exports = start; 33 | module.exports.EventSymbol = EventSymbol; 34 | -------------------------------------------------------------------------------- /instrumenting/http-in.js: -------------------------------------------------------------------------------- 1 | import { HTTP_INCOMING } from '../constants'; 2 | 3 | function start(agent, WebApp) { 4 | WebApp.connectHandlers.use(function(req, res, next) { 5 | const transaction = agent.startTransaction(`${req.method}:${req.url}`, HTTP_INCOMING); 6 | if (transaction) { 7 | transaction.setLabel('url', `${req.url}`); 8 | transaction.setLabel('method', `${req.method}`); 9 | } 10 | 11 | res.on('finish', () => { 12 | let route = req.originalUrl; 13 | if (req.originalUrl.endsWith(req.url.slice(1)) && req.url.length > 1) { 14 | route = req.originalUrl.slice(0, -1 * (req.url.length - 1)); 15 | } 16 | 17 | if (route.endsWith('/')) { 18 | route = route.slice(0, -1); 19 | } 20 | 21 | if (route && transaction) { 22 | transaction.name = `${req.method}:${route}`; 23 | transaction.setLabel('route', `${route}`); 24 | } 25 | 26 | if (transaction) { 27 | transaction.end(); 28 | } 29 | }); 30 | 31 | next(); 32 | }); 33 | } 34 | 35 | module.exports = start; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kengessov Shynggys 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional eslint cache 43 | .eslintcache 44 | 45 | # Optional REPL history 46 | .node_repl_history 47 | 48 | # Output of 'npm pack' 49 | *.tgz 50 | 51 | # Yarn Integrity file 52 | .yarn-integrity 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # next.js build output 58 | .next 59 | 60 | # meteor package versions 61 | .versions 62 | .idea/ 63 | -------------------------------------------------------------------------------- /__tests__/asyncInstrument.test.js: -------------------------------------------------------------------------------- 1 | const newFibers = require('./mocks/fibers'); 2 | const instrumentAsync = require('../instrumenting/async'); 3 | const newAgent = require('./mocks/agent'); 4 | 5 | test('track async execution', () => { 6 | const Fibers = newFibers(); 7 | const agent = newAgent(); 8 | 9 | agent.currentTransaction = { 10 | name: 'test' 11 | }; 12 | instrumentAsync(agent, Fibers); 13 | Fibers.yield(); 14 | 15 | expect(Fibers.current[instrumentAsync.EventSymbol]).toBeDefined(); 16 | 17 | Fibers.current.run(); 18 | 19 | expect(Fibers.current[instrumentAsync.EventSymbol]).toBeUndefined(); 20 | 21 | expect(agent.startSpan.mock.calls.length).toBe(1); 22 | }); 23 | 24 | test('ignore invalid parents', () => { 25 | const Fibers = newFibers(); 26 | const agent = newAgent(); 27 | 28 | agent.currentTransaction = { 29 | name: 'test' 30 | }; 31 | agent.currentSpan = { 32 | type: 'db' 33 | }; 34 | 35 | instrumentAsync(agent, Fibers); 36 | Fibers.yield(); 37 | 38 | expect(Fibers.current[instrumentAsync.EventSymbol]).toBeUndefined(); 39 | 40 | Fibers.current.run(); 41 | 42 | expect(Fibers.current[instrumentAsync.EventSymbol]).toBeUndefined(); 43 | 44 | expect(agent.startSpan.mock.calls.length).toBe(0); 45 | }); 46 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | Package.describe({ 3 | name: 'kschingiz:meteor-elastic-apm', 4 | version: '2.5.2', 5 | // Brief, one-line summary of the package. 6 | summary: 'Performance monitoring for Meteor based on Elastic APM', 7 | // URL to the Git repository containing the source code for this package. 8 | git: 'https://github.com/Meteor-Community-Packages/meteor-elastic-apm', 9 | // By default, Meteor will default to using README.md for documentation. 10 | // To avoid submitting documentation, set this field to null. 11 | documentation: 'README.md' 12 | }); 13 | 14 | Npm.depends({ 15 | 'elastic-apm-node': '3.15.0', 16 | shimmer: '1.2.1' 17 | }); 18 | 19 | Package.onUse(function(api) { 20 | api.versionsFrom('2.4'); 21 | 22 | api.use('kschingiz:meteor-measured@1.0.3'); 23 | api.imply('kschingiz:meteor-measured'); 24 | 25 | api.use([ 26 | 'ecmascript', 27 | 'mongo', 28 | 'minimongo', 29 | 'ddp', 30 | 'ddp-common', 31 | 'webapp', 32 | 'random' 33 | ]); 34 | 35 | api.mainModule('meteor-elastic-apm.js', 'server'); 36 | }); 37 | 38 | Package.onTest(function(api) { 39 | api.use('ecmascript'); 40 | api.use('tinytest'); 41 | api.use('kschingiz:meteor-elastic-apm'); 42 | api.mainModule('meteor-elastic-apm-tests.js'); 43 | }); 44 | -------------------------------------------------------------------------------- /hacks.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-spread */ 2 | /* eslint-disable prefer-rest-params */ 3 | /* eslint-disable no-undef */ 4 | // This hijack is important to make sure, collections created before 5 | // we hijack dbOps, even gets tracked. 6 | // Meteor does not simply expose MongoConnection object to the client 7 | // It picks methods which are necessory and make a binded object and 8 | // assigned to the Mongo.Collection 9 | // so, even we updated prototype, we can't track those collections 10 | // but, this will fix it. 11 | module.exports = function hack() { 12 | const originalOpen = MongoInternals.RemoteCollectionDriver.prototype.open; 13 | MongoInternals.RemoteCollectionDriver.prototype.open = function open(name) { 14 | const self = this; 15 | const ret = originalOpen.call(self, name); 16 | 17 | Object.keys(ret).forEach(function(m) { 18 | // make sure, it's in the actual mongo connection object 19 | // meteorhacks:mongo-collection-utils package add some arbitary methods 20 | // which does not exist in the mongo connection 21 | if (self.mongo[m]) { 22 | ret[m] = function() { 23 | Array.prototype.unshift.call(arguments, name); 24 | 25 | return self.mongo[m].apply(self.mongo, arguments); 26 | }; 27 | } 28 | }); 29 | 30 | return ret; 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-elastic-apm", 3 | "version": "2.5.2", 4 | "description": "Performance Monitoring for Meteor based on Elastic APM", 5 | "main": "meteor-elastic-apm.js", 6 | "scripts": { 7 | "lint": "eslint ./", 8 | "test": "jest --coverage", 9 | "report-coverage": "codecov", 10 | "publish": "meteor npm i && npm prune --production && meteor publish && meteor npm i" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Meteor-Community-Packages/meteor-elastic-apm.git" 15 | }, 16 | "author": "Shynggys Kengessov", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/Meteor-Community-Packages/meteor-elastic-apm/issues" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.22.20", 23 | "babel-jest": "^25.5.1", 24 | "babel-preset-env": "^1.7.0", 25 | "codecov": "^3.8.3", 26 | "eslint": "^8.50.0", 27 | "eslint-config-airbnb-base": "^15.0.0", 28 | "eslint-config-prettier": "^9.0.0", 29 | "eslint-plugin-import": "^2.28.1", 30 | "eslint-plugin-prettier": "^5.0.0", 31 | "jest": "^29.7.0", 32 | "prettier": "3.0.3" 33 | }, 34 | "homepage": "https://github.com/kschingiz/meteor-elastic-apm#readme", 35 | "dependencies": { 36 | "elastic-apm-node": "3.15.0", 37 | "fibers": "^4.0.3", 38 | "shimmer": "1.2.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/comment-issue.yml: -------------------------------------------------------------------------------- 1 | name: Add immediate comment on new issues 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | createComment: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Create Comment 12 | uses: peter-evans/create-or-update-comment@v1.4.2 13 | with: 14 | issue-number: ${{ github.event.issue.number }} 15 | body: | 16 | Thank you for submitting this issue! 17 | 18 | We, the Members of Meteor Community Packages take every issue seriously. 19 | Our goal is to provide long-term lifecycles for packages and keep up 20 | with the newest changes in Meteor and the overall NodeJs/JavaScript ecosystem. 21 | 22 | However, we contribute to these packages mostly in our free time. 23 | Therefore, we can't guarantee you issues to be solved within certain time. 24 | 25 | If you think this issue is trivial to solve, don't hesitate to submit 26 | a pull request, too! We will accompany you in the process with reviews and hints 27 | on how to get development set up. 28 | 29 | Please also consider sponsoring the maintainers of the package. 30 | If you don't know who is currently maintaining this package, just leave a comment 31 | and we'll let you know 32 | -------------------------------------------------------------------------------- /instrumenting/http-out.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import url from 'url'; 3 | import shimmer from 'shimmer'; 4 | 5 | import { HTTP, HTTP_OUTGOING } from '../constants'; 6 | 7 | function start(agent) { 8 | shimmer.wrap(http, 'request', function(original) { 9 | return function(options, callback) { 10 | // we don't want to catch elastic requests, it causes recursive requests handling 11 | const userAgent = (options && options.headers ? options.headers['User-Agent'] : '') || ''; 12 | if (userAgent.includes('elastic-apm')) { 13 | return original.call(this, options, callback); 14 | } 15 | 16 | const apmOptions = typeof options === 'string' ? url.parse(options) : options; 17 | 18 | const { method, path } = apmOptions; 19 | let { host } = apmOptions; 20 | 21 | if (!host) host = apmOptions.hostname; 22 | 23 | const eventName = `${method}://${host}${path}`; 24 | const eventType = HTTP_OUTGOING; 25 | const transaction = agent.currentTransaction || agent.startTransaction(eventName, eventType); 26 | const span = agent.startSpan(eventName, HTTP); 27 | 28 | if (transaction) { 29 | transaction.__span = span; 30 | } 31 | 32 | const request = original.call(this, options, callback); 33 | 34 | const requestEnd = function(error) { 35 | if (error) { 36 | agent.captureError(error); 37 | } 38 | if (transaction) { 39 | if (transaction.__span) { 40 | transaction.__span.end(); 41 | } 42 | if (transaction.type === HTTP_OUTGOING) { 43 | transaction.end(); 44 | } 45 | } 46 | }; 47 | 48 | request.on('error', requestEnd); 49 | request.on('response', function(response) { 50 | response.on('end', requestEnd); 51 | response.on('error', requestEnd); 52 | }); 53 | 54 | return request; 55 | }; 56 | }); 57 | } 58 | 59 | module.exports = start; 60 | -------------------------------------------------------------------------------- /instrumenting/subscription.js: -------------------------------------------------------------------------------- 1 | import shimmer from 'shimmer'; 2 | 3 | function start(agent, Subscription) { 4 | function wrapSubscription(subscriptionProto) { 5 | shimmer.wrap(subscriptionProto, '_runHandler', function(original) { 6 | return function(...args) { 7 | this.__transaction = agent.currentTransaction; 8 | return original.apply(this, args); 9 | }; 10 | }); 11 | 12 | shimmer.wrap(subscriptionProto, 'ready', function(original) { 13 | return function(...args) { 14 | const transaction = this.__transaction; 15 | if (transaction) { 16 | if (transaction.__span) { 17 | transaction.__span.end(); 18 | } 19 | 20 | const { _documents } = this; 21 | 22 | const sentCollectionDocs = Object.keys(_documents).reduce((acc, collectionName) => { 23 | const keys = Object.keys(_documents[collectionName]); 24 | acc[collectionName] = keys.length; 25 | 26 | return acc; 27 | }, {}); 28 | 29 | transaction.addLabels(sentCollectionDocs); 30 | transaction.end('ready'); 31 | this.__transaction = undefined; 32 | } 33 | 34 | return original.apply(this, args); 35 | }; 36 | }); 37 | 38 | shimmer.wrap(subscriptionProto, 'error', function(original) { 39 | return function(err) { 40 | const transaction = this.__transaction; 41 | if (transaction) { 42 | if (transaction.__span) { 43 | transaction.__span.end(); 44 | } 45 | agent.captureError(err); 46 | transaction.addLabels({ 47 | exception: JSON.stringify({ 48 | message: err.message, 49 | stack: err.stack 50 | }) 51 | }); 52 | 53 | transaction.end('fail'); 54 | this.__transaction = undefined; 55 | } 56 | 57 | return original.call(this, err); 58 | }; 59 | }); 60 | } 61 | 62 | wrapSubscription(Subscription.prototype); 63 | } 64 | 65 | module.exports = start; 66 | -------------------------------------------------------------------------------- /instrumenting/methods.js: -------------------------------------------------------------------------------- 1 | import shimmer from 'shimmer'; 2 | 3 | function closeTransaction(agent, exception, result) { 4 | const { currentTransaction } = agent; 5 | if (currentTransaction) { 6 | if (currentTransaction.__span) { 7 | currentTransaction.__span.end(); 8 | currentTransaction.__span = undefined; 9 | } 10 | 11 | if (exception) { 12 | agent.captureError(exception); 13 | currentTransaction.addLabels({ 14 | status: 'fail', 15 | exception: JSON.stringify({ 16 | stack: exception.stack, 17 | message: exception.message 18 | }) 19 | }); 20 | } else { 21 | currentTransaction.addLabels({ 22 | status: 'success', 23 | result: JSON.stringify(result) 24 | }); 25 | } 26 | 27 | currentTransaction.end(exception ? 'fail' : 'success'); 28 | } 29 | } 30 | 31 | function start(agent, Meteor) { 32 | function wrapMethods(name, methodMap) { 33 | shimmer.wrap(methodMap, name, function(originalHandler) { 34 | return function(...args) { 35 | try { 36 | const result = originalHandler.apply(this, args); 37 | 38 | closeTransaction(agent, null, result); 39 | 40 | return result; 41 | } catch (ex) { 42 | if (typeof ex !== 'object') { 43 | // eslint-disable-next-line no-ex-assign 44 | ex = { message: ex, stack: ex }; 45 | } 46 | ex.stack = { stack: ex.stack, source: 'method' }; 47 | 48 | closeTransaction(agent, ex, null); 49 | throw ex; 50 | } 51 | }; 52 | }); 53 | } 54 | 55 | const methodHandlers = Meteor.server.method_handlers; 56 | Object.keys(methodHandlers).forEach(methodName => wrapMethods(methodName, methodHandlers)); 57 | 58 | shimmer.wrap(Meteor, 'methods', function(original) { 59 | return function(methodsMap) { 60 | Object.keys(methodsMap).forEach(methodName => wrapMethods(methodName, methodsMap)); 61 | 62 | return original.apply(this, [methodsMap]); 63 | }; 64 | }); 65 | } 66 | 67 | module.exports = start; 68 | -------------------------------------------------------------------------------- /meteor-elastic-apm.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const shimmer = require('shimmer'); 3 | const Fibers = require('fibers'); 4 | const Agent = require('elastic-apm-node'); 5 | 6 | const { Session, Subscription, MongoCursor } = require('./meteorx'); 7 | 8 | const instrumentMethods = require('./instrumenting/methods'); 9 | const instrumentHttpIn = require('./instrumenting/http-in'); 10 | const instrumentHttpOut = require('./instrumenting/http-out'); 11 | const instrumentSession = require('./instrumenting/session'); 12 | const instrumentSubscription = require('./instrumenting/subscription'); 13 | const instrumentAsync = require('./instrumenting/async'); 14 | const instrumentDB = require('./instrumenting/db'); 15 | const startMetrics = require('./metrics'); 16 | 17 | const hackDB = require('./hacks'); 18 | 19 | const [framework, version] = Meteor.release.split('@'); 20 | 21 | Agent.setFramework({ 22 | name: framework, 23 | version, 24 | override: true 25 | }); 26 | 27 | shimmer.wrap(Agent, 'start', function(startAgent) { 28 | return function(...args) { 29 | const config = args[0] || {}; 30 | 31 | if (config.active !== false) { 32 | try { 33 | const disabled = new Set(config.disableMeteorInstrumentations); 34 | 35 | // Must be called before any other route is registered on WebApp. 36 | instrumentHttpIn(Agent, WebApp); 37 | instrumentHttpOut(Agent); 38 | 39 | Meteor.startup(() => { 40 | try { 41 | startAgent.apply(Agent, args); 42 | 43 | Object.entries({ 44 | methods: () => instrumentMethods(Agent, Meteor), 45 | session: () => instrumentSession(Agent, Session), 46 | subscription: () => instrumentSubscription(Agent, Subscription), 47 | async: () => instrumentAsync(Agent, Fibers), 48 | db: () => { 49 | hackDB(); 50 | instrumentDB(Agent, Meteor, MongoCursor); 51 | }, 52 | metrics: () => startMetrics(Agent) 53 | }).forEach(([name, fn]) => { 54 | if (!disabled.has(name)) { 55 | fn(); 56 | } 57 | }); 58 | 59 | Agent.logger.info('meteor-elastic-apm completed instrumenting'); 60 | } catch (e) { 61 | Agent.logger.error('Could not start meteor-elastic-apm'); 62 | throw e; 63 | } 64 | }); 65 | } catch (e) { 66 | Agent.logger.error('Could not start meteor-elastic-apm'); 67 | throw e; 68 | } 69 | } else { 70 | Agent.logger.warn('meteor-elastic-apm is not active'); 71 | } 72 | }; 73 | }); 74 | 75 | module.exports = Agent; 76 | -------------------------------------------------------------------------------- /METRICS.md: -------------------------------------------------------------------------------- 1 | # Meteor metrics 2 | 3 | From version 2.2.0 Meteor-elastic-apm by default comes with [meteor-measured](https://github.com/kschingiz/meteor-measured). 4 | 5 | Meteor-measured sends a lot of useful meteor specific metrics: 6 | 7 | Sockets Metrics: 8 | 9 | 1. Open sockets: 10 sockets open 10 | 2. Live data sockets: 8 sockets uses livedata 11 | 12 | Session Metrics: 13 | 14 | 1. Sessions count: 8 meteor sessions 15 | 16 | Pub/sub Metrics: 17 | 18 | 1. Subscriptions count: 100 subscriptions 19 | 2. Subscriptions count for each publication: testPub: 20 subs, notTestPub: 80 subs 20 | 3. Published documents: 20 docs published 21 | 4. Published documents for each collection: 10 docs of TestCollection published, 10 docs of NotTestCollection published 22 | 23 | Observer Metrics: 24 | 25 | 1. Number of observers: 20 observers created 26 | 2. Number Observer drivers: Oplog drivers: 10, Polling drivers: 10 27 | 3. Number of documents for each driver: TestCollection published by oplog driver: 10 28 | 29 | Meteor-measured uses elastic agent registry and sends metrics data directly to kibana. 30 | 31 | ## How to visualize metrics 32 | 33 | 1. Start your app with metrics, wait for some time 2-3 minutes, do something in your app to collect and send metrics. We need to wait while `elasticsearch` will save and index our metrics. 34 | 2. Go to Kibana -> Management -> Index Patterns -> `apm-*` 35 | You will see a list of all indexed fields, click on `Refresh Fields list` button, the button is located on top-right corner: 36 | ![index_pattern](./assets/index-pattern.png) 37 | 38 | Sorry for my amazing drawing skills. 39 | 40 | 3. After Refreshing was completed try to find some of the fields which we are sending: `sessions.count`, `sockets.open`, `sockets.liveData`. 41 | 42 | 4. Time to Visualize our metrics: Go to Kibana -> Visualize -> Create Visualization. 43 | 44 | Select `Line` chart, index pattern should be `apm-*` 45 | 46 | 5. You need to configure the chart to visualize meteor metric data. Let's draw `sessions.count` metrics, to do so you should configure chart this way: 47 | Y-Axis props: 48 | 49 | ``` 50 | Aggregation = Max 51 | Field = sessions.count 52 | Custom Label = Sessions 53 | ``` 54 | 55 | X-Axis props: 56 | 57 | ``` 58 | Aggregation = Date Histogram 59 | Field = @timestamp 60 | ``` 61 | 62 | 7. Click on blue "Apply Changes" button. 63 | 8. Configure time range to be: `30 minutes ago`. Click on "Refresh" 64 | 65 | Repeat steps for every metric field you need, you can combine charts in Dashboard later. 66 | 67 | Sample sessions count chart: 68 | ![sessions_count](./assets/sessions_count.png) 69 | 70 | PS: I am not elastic search, kibana expert, maybe there is easier steps to do the same. If you want to correct something or add useful tips and tricks, I am waiting for your PRs 71 | -------------------------------------------------------------------------------- /meteorx.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // Various tricks for accessing "private" Meteor APIs borrowed from the 3 | // now-unmaintained meteorhacks:meteorx package. 4 | 5 | export const Server = Meteor.server.constructor; 6 | 7 | function getSession() { 8 | const fakeSocket = { 9 | send() {}, 10 | close() {}, 11 | headers: [] 12 | }; 13 | 14 | const server = Meteor.server; 15 | 16 | server._handleConnect(fakeSocket, { 17 | msg: 'connect', 18 | version: 'pre1', 19 | support: ['pre1'] 20 | }); 21 | 22 | const session = fakeSocket._meteorSession; 23 | 24 | server._removeSession(session); 25 | 26 | return session; 27 | } 28 | 29 | const session = getSession(); 30 | export const Session = session.constructor; 31 | 32 | const collection = new Mongo.Collection(`__dummy_coll_${Random.id()}`); 33 | collection.findOne(); 34 | const cursor = collection.find(); 35 | export const MongoCursor = cursor.constructor; 36 | 37 | function getMultiplexer(multiCursor) { 38 | const handle = multiCursor.observeChanges({ 39 | added() {} 40 | }); 41 | handle.stop(); 42 | return handle._multiplexer; 43 | } 44 | 45 | export const Multiplexer = getMultiplexer(cursor).constructor; 46 | 47 | export const MongoConnection = MongoInternals.defaultRemoteCollectionDriver().mongo.constructor; 48 | 49 | function getSubscription(subSession) { 50 | const subId = Random.id(); 51 | 52 | subSession._startSubscription( 53 | function() { 54 | this.ready(); 55 | }, 56 | subId, 57 | [], 58 | `__dummy_pub_${Random.id()}` 59 | ); 60 | 61 | const subscription = 62 | subSession._namedSubs instanceof Map 63 | ? subSession._namedSubs.get(subId) 64 | : subSession._namedSubs[subId]; 65 | 66 | subSession._stopSubscription(subId); 67 | 68 | return subscription; 69 | } 70 | 71 | export const Subscription = getSubscription(session).constructor; 72 | 73 | function getObserverDriver(obsCursor) { 74 | const multiplexer = getMultiplexer(obsCursor); 75 | return (multiplexer && multiplexer._observeDriver) || null; 76 | } 77 | 78 | function getMongoOplogDriver() { 79 | const driver = getObserverDriver(cursor); 80 | const MongoOplogDriver = (driver && driver.constructor) || null; 81 | if (MongoOplogDriver && typeof MongoOplogDriver.cursorSupported !== 'function') { 82 | return null; 83 | } 84 | return MongoOplogDriver; 85 | } 86 | 87 | export const MongoOplogDriver = getMongoOplogDriver(); 88 | 89 | function getMongoPollingDriver() { 90 | const driverCursor = collection.find( 91 | {}, 92 | { 93 | limit: 20, 94 | _disableOplog: true 95 | } 96 | ); 97 | 98 | const driver = getObserverDriver(driverCursor); 99 | 100 | // verify observer driver is a polling driver 101 | if (driver && typeof driver.constructor.cursorSupported === 'undefined') { 102 | return driver.constructor; 103 | } 104 | 105 | return null; 106 | } 107 | 108 | export const MongoPollingDriver = getMongoPollingDriver(); 109 | -------------------------------------------------------------------------------- /instrumenting/session.js: -------------------------------------------------------------------------------- 1 | import shimmer from 'shimmer'; 2 | 3 | function start(agent, Session) { 4 | function wrapSession(sessionProto) { 5 | shimmer.wrap(sessionProto, 'processMessage', function(original) { 6 | return function(msg) { 7 | if ( 8 | (msg.msg === 'method' && 9 | // _FilesCollectionWrite_ is a prefix for meteor-methods used by meteor/ostrio:files which is used to 10 | // send files via DDP to the server. Monitoring these routes send the file - byte-serialized - to the monitoring system. 11 | !msg.method.startsWith('_FilesCollectionWrite_')) || 12 | msg.msg === 'sub' 13 | ) { 14 | const name = msg.msg === 'method' ? msg.method : msg.name; 15 | const type = msg.msg; 16 | const transaction = agent.startTransaction(name, type); 17 | 18 | agent.setCustomContext(msg.params || {}); 19 | agent.setUserContext({ id: this.userId || 'Not authorized' }); 20 | 21 | if (transaction) { 22 | transaction.addLabels({ 23 | params: JSON.stringify(msg.params) 24 | }); 25 | 26 | transaction.__span = agent.startSpan('wait'); 27 | 28 | msg.__transaction = transaction; 29 | } 30 | } 31 | 32 | return original.call(this, msg); 33 | }; 34 | }); 35 | 36 | shimmer.wrap(sessionProto.protocol_handlers, 'method', function(original) { 37 | return function(msg, unblock) { 38 | if (msg.__transaction) { 39 | if (msg.__transaction.__span) { 40 | msg.__transaction.__span.end(); 41 | msg.__transaction.__span = undefined; 42 | } 43 | 44 | msg.__transaction.__span = agent.startSpan('execution'); 45 | } 46 | 47 | return original.call(this, msg, unblock); 48 | }; 49 | }); 50 | 51 | shimmer.wrap(sessionProto.protocol_handlers, 'sub', function(original) { 52 | return function(msg, unblock) { 53 | const self = this; 54 | if (msg.__transaction) { 55 | if (msg.__transaction.__span) { 56 | msg.__transaction.__span.end(); 57 | } 58 | 59 | msg.__transaction.__span = agent.startSpan('execution'); 60 | } 61 | 62 | const result = original.call(self, msg, unblock); 63 | 64 | return result; 65 | }; 66 | }); 67 | 68 | shimmer.wrap(sessionProto.protocol_handlers, 'unsub', function(original) { 69 | return function(msg, unblock) { 70 | if (msg.__transaction) { 71 | if (msg.__transaction.__span) { 72 | msg.__transaction.__span.end(); 73 | } 74 | 75 | msg.__transaction.__span = agent.startSpan('execution'); 76 | } 77 | 78 | const response = original.call(this, msg, unblock); 79 | 80 | if (msg.__transaction) { 81 | if (msg.__transaction.__span) { 82 | msg.__transaction.__span.end(); 83 | } 84 | msg.__transaction.end(); 85 | } 86 | return response; 87 | }; 88 | }); 89 | } 90 | 91 | wrapSession(Session.prototype); 92 | } 93 | 94 | module.exports = start; 95 | -------------------------------------------------------------------------------- /__tests__/subscriptionInstrument.test.js: -------------------------------------------------------------------------------- 1 | const instrumentSubscription = require('../instrumenting/subscription'); 2 | const newAgent = require('./mocks/agent'); 3 | const newSubscription = require('./mocks/subscription'); 4 | 5 | test('track when subscription is run', () => { 6 | const Subscription = newSubscription(); 7 | const agent = newAgent(); 8 | 9 | agent.currentTransaction = { 10 | name: 'test' 11 | }; 12 | 13 | instrumentSubscription(agent, Subscription); 14 | const sub = new Subscription(); 15 | 16 | sub._runHandler(); 17 | 18 | expect(sub.__transaction).toBe(agent.currentTransaction); 19 | }); 20 | 21 | test('ignore track if current transaction is undefined', () => { 22 | const Subscription = newSubscription(); 23 | const agent = newAgent(); 24 | 25 | instrumentSubscription(agent, Subscription); 26 | 27 | const sub = new Subscription(); 28 | 29 | sub._runHandler(); 30 | 31 | expect(sub.__transaction).toBeUndefined(); 32 | }); 33 | 34 | test('close transaction when sub is ready', () => { 35 | const Subscription = newSubscription(); 36 | const agent = newAgent(); 37 | 38 | const transaction = agent.startTransaction(); 39 | 40 | agent.currentTransaction = transaction; 41 | 42 | instrumentSubscription(agent, Subscription); 43 | const sub = new Subscription(); 44 | 45 | sub._runHandler(); 46 | 47 | sub.ready(); 48 | 49 | expect(sub.__transaction).toBeUndefined(); 50 | expect(transaction.end.mock.calls.length).toBe(1); 51 | }); 52 | 53 | test('close transaction and its span when sub is ready', () => { 54 | const Subscription = newSubscription(); 55 | const agent = newAgent(); 56 | 57 | const transaction = agent.startTransaction(); 58 | const waitSpan = agent.startSpan(); 59 | 60 | transaction.__span = waitSpan; 61 | agent.currentTransaction = transaction; 62 | 63 | instrumentSubscription(agent, Subscription); 64 | const sub = new Subscription(); 65 | 66 | sub._runHandler(); 67 | 68 | sub.ready(); 69 | 70 | expect(sub.__transaction).toBeUndefined(); 71 | expect(transaction.end.mock.calls.length).toBe(1); 72 | expect(waitSpan.end.mock.calls.length).toBe(1); 73 | }); 74 | 75 | test('ignore track if transaction is undefined when sub is ready', () => { 76 | const Subscription = newSubscription(); 77 | const agent = newAgent(); 78 | 79 | instrumentSubscription(agent, Subscription); 80 | const sub = new Subscription(); 81 | 82 | sub._runHandler(); 83 | 84 | sub.ready(); 85 | 86 | expect(sub.__transaction).toBeUndefined(); 87 | }); 88 | 89 | test('close transaction and capture error if sub throws error', () => { 90 | const Subscription = newSubscription(); 91 | const agent = newAgent(); 92 | 93 | const transaction = agent.startTransaction(); 94 | 95 | agent.currentTransaction = transaction; 96 | 97 | instrumentSubscription(agent, Subscription); 98 | const sub = new Subscription(); 99 | 100 | sub._runHandler(); 101 | 102 | sub.error(new Error('test error')); 103 | 104 | expect(sub.__transaction).toBeUndefined(); 105 | expect(transaction.end.mock.calls.length).toBe(1); 106 | expect(agent.captureError.mock.calls.length).toBe(1); 107 | }); 108 | 109 | test('close transaction/span and capture error if sub throws error', () => { 110 | const Subscription = newSubscription(); 111 | const agent = newAgent(); 112 | 113 | const transaction = agent.startTransaction(); 114 | const waitSpan = agent.startSpan(); 115 | 116 | transaction.__span = waitSpan; 117 | agent.currentTransaction = transaction; 118 | 119 | instrumentSubscription(agent, Subscription); 120 | const sub = new Subscription(); 121 | 122 | sub._runHandler(); 123 | 124 | sub.error(new Error('test error')); 125 | 126 | expect(sub.__transaction).toBeUndefined(); 127 | expect(transaction.end.mock.calls.length).toBe(1); 128 | expect(waitSpan.end.mock.calls.length).toBe(1); 129 | expect(agent.captureError.mock.calls.length).toBe(1); 130 | }); 131 | 132 | test('ignore track if transaction is undefined on sub error', () => { 133 | const Subscription = newSubscription(); 134 | const agent = newAgent(); 135 | 136 | instrumentSubscription(agent, Subscription); 137 | const sub = new Subscription(); 138 | 139 | sub._runHandler(); 140 | 141 | sub.error(); 142 | 143 | expect(sub.__transaction).toBeUndefined(); 144 | }); 145 | -------------------------------------------------------------------------------- /__tests__/methodsInstrument.js: -------------------------------------------------------------------------------- 1 | const instrumentMethods = require('../instrumenting/methods'); 2 | const newAgent = require('./mocks/agent'); 3 | const newMeteor = require('./mocks/meteor'); 4 | 5 | test('close transaction with method result', () => { 6 | const Meteor = newMeteor(); 7 | 8 | Meteor.methods({ 9 | method1() { 10 | return 'test result'; 11 | } 12 | }); 13 | 14 | const agent = newAgent(); 15 | 16 | const transaction = agent.startTransaction(); 17 | const span = agent.startSpan(); 18 | 19 | agent.currentTransaction = transaction; 20 | agent.currentTransaction.__span = span; 21 | 22 | instrumentMethods(agent, Meteor); 23 | 24 | Meteor.methods({ 25 | method2() { 26 | return 'test result 2'; 27 | } 28 | }); 29 | 30 | Meteor.server.method_handlers.method1(); 31 | 32 | expect(agent.currentTransaction.end.mock.calls.length).toBe(1); 33 | expect(agent.currentTransaction.end.mock.calls[0][0]).toBe('success'); 34 | 35 | Meteor.server.method_handlers.method2(); 36 | 37 | expect(agent.currentTransaction.end.mock.calls.length).toBe(2); 38 | expect(agent.currentTransaction.end.mock.calls[1][0]).toBe('success'); 39 | }); 40 | 41 | test('ignore if transaction is undefined', () => { 42 | const Meteor = newMeteor(); 43 | 44 | Meteor.methods({ 45 | method1() { 46 | return 'test result'; 47 | } 48 | }); 49 | 50 | const agent = newAgent(); 51 | 52 | instrumentMethods(agent, Meteor); 53 | 54 | Meteor.server.method_handlers.method1(); 55 | }); 56 | 57 | test('close transaction and its span with method result', () => { 58 | const Meteor = newMeteor(); 59 | 60 | Meteor.methods({ 61 | method1() { 62 | return 'test result'; 63 | } 64 | }); 65 | 66 | const agent = newAgent(); 67 | 68 | const transaction = agent.startTransaction(); 69 | const span = agent.startSpan(); 70 | 71 | agent.currentTransaction = transaction; 72 | agent.currentTransaction.__span = span; 73 | 74 | instrumentMethods(agent, Meteor); 75 | 76 | Meteor.methods({ 77 | method2() { 78 | return 'test result 2'; 79 | } 80 | }); 81 | 82 | Meteor.server.method_handlers.method1(); 83 | 84 | expect(agent.currentTransaction.end.mock.calls.length).toBe(1); 85 | expect(agent.currentTransaction.end.mock.calls[0][0]).toBe('success'); 86 | expect(agent.currentTransaction.__span).toBeUndefined(); 87 | expect(span.end.mock.calls.length).toBe(1); 88 | }); 89 | 90 | test('catch meteor method exception', () => { 91 | const Meteor = newMeteor(); 92 | 93 | Meteor.methods({ 94 | method1() { 95 | throw new Error('Test error 1'); 96 | } 97 | }); 98 | 99 | const agent = newAgent(); 100 | 101 | const transaction = agent.startTransaction(); 102 | const span = agent.startSpan(); 103 | 104 | agent.currentTransaction = transaction; 105 | agent.currentTransaction.__span = span; 106 | 107 | instrumentMethods(agent, Meteor); 108 | 109 | Meteor.methods({ 110 | method2() { 111 | throw new Error('Test error 2'); 112 | } 113 | }); 114 | 115 | expect(() => { 116 | Meteor.server.method_handlers.method1(); 117 | }).toThrow(); 118 | 119 | expect(agent.captureError.mock.calls.length).toBe(1); 120 | expect(agent.captureError.mock.calls[0][0].message).toBe('Test error 1'); 121 | 122 | expect(() => { 123 | Meteor.server.method_handlers.method2(); 124 | }).toThrow(); 125 | 126 | expect(agent.captureError.mock.calls.length).toBe(2); 127 | expect(agent.captureError.mock.calls[1][0].message).toBe('Test error 2'); 128 | }); 129 | 130 | test('transform string exception into Error object', () => { 131 | const Meteor = newMeteor(); 132 | 133 | Meteor.methods({ 134 | textError() { 135 | // eslint-disable-next-line no-throw-literal 136 | throw 'Test error 1'; 137 | } 138 | }); 139 | 140 | const agent = newAgent(); 141 | 142 | const transaction = agent.startTransaction(); 143 | const span = agent.startSpan(); 144 | 145 | agent.currentTransaction = transaction; 146 | agent.currentTransaction.__span = span; 147 | 148 | instrumentMethods(agent, Meteor); 149 | 150 | expect(() => { 151 | Meteor.server.method_handlers.textError(); 152 | }).toThrow(); 153 | 154 | expect(agent.captureError.mock.calls.length).toBe(1); 155 | expect(agent.captureError.mock.calls[0][0].message).toBe('Test error 1'); 156 | }); 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meteor-elastic-apm 2 | 3 | [![Build Status](https://travis-ci.org/kschingiz/meteor-elastic-apm.svg?branch=master)](https://travis-ci.org/kschingiz/meteor-elastic-apm) 4 | [![codecov](https://codecov.io/gh/kschingiz/meteor-elastic-apm/branch/master/graph/badge.svg)](https://codecov.io/gh/kschingiz/meteor-elastic-apm) 5 | 6 | ### Performance Monitoring for Meteor based on Elastic APM 7 | 8 | [![Meteor Elastic APM screenshot](https://raw.githubusercontent.com/kschingiz/meteor-elastic-apm/master/assets/meteor-call-2.png)](https://github.com/kschingiz/meteor-elastic-apm) 9 | 10 | ## Getting started 11 | 12 | 1. Install and configure elasticsearch - https://www.elastic.co/downloads/elasticsearch 13 | 2. Install and configure Kibana - https://www.elastic.co/downloads/kibana 14 | 3. Install and configure elastic APM server - https://www.elastic.co/downloads/apm 15 | 16 | Then in your Meteor project 17 | 18 | ```bash 19 | meteor add kschingiz:meteor-elastic-apm 20 | ``` 21 | 22 | Maybe you will need to also install 23 | 24 | ```bash 25 | meteor add http mongo-livequery 26 | ``` 27 | 28 | Then somewhere in your server code, Elastic documentation says that Agent.start should be executed before anything else, and should be at the very top of your code 29 | 30 | ```js 31 | import Agent from 'meteor/kschingiz:meteor-elastic-apm'; 32 | 33 | const options = { 34 | serviceName: 'meteor-demo-app' 35 | }; 36 | Agent.start(options); 37 | ``` 38 | 39 | Complete list of [Agent options](https://www.elastic.co/guide/en/apm/agent/nodejs/current/advanced-setup.html) 40 | 41 | In addition, this plugin supports the following Agent options: 42 | 43 | * active - Boolean value which determins if monitoring is active. Default: `true` 44 | * disableMeteorInstrumentations - An array of meteor related instrumentations which should not be recorded. Default: `[]`. Possible values: `['methods', 'session', 'subscription', 'async', 'db, 'metrics']` 45 | 46 | ## What it monitors 47 | 48 | 1. Meteor methods: method params, result, exceptions, stack trace 49 | * Ignores methods starting with `_FilesCollectionWrite_`. (See: https://github.com/Meteor-Community-Packages/meteor-elastic-apm/issues/30) 50 | 2. Meteor pub/sub: tracks publications response time, params, exceptions, result 51 | 3. Meteor collection methods(find, insert, etc...): params, result, execution time 52 | 4. MongoDB cursor method(fetch, map, etc...): params, result, execution time 53 | 5. Trace async execution 54 | 6. All Incoming and outgoing HTTP requests, useful if you have REST API 55 | 7. Exception handling 56 | 57 | ## Metrics 58 | 59 | From version 2.2.0 the package collects and sends meteor specific metrics to the apm-server. 60 | You can learn how it works and how to use it in [Metrics docs](./METRICS.md) 61 | 62 | ## Performance 63 | 64 | If you discover significant performance implications, you can disable any of the metrics by adding the configuration `disableMeteorInstrumentations` 65 | and specifying in an array which of the metrics you want to disable: `['methods', 'session', 'subscription', 'async', 'db, 'metrics']`. 66 | 67 | Please also have a look at the documentation of the underlying library [`apm-agent-nodejs`](https://github.com/elastic/apm-agent-nodejs) 68 | 69 | ## Screenshots 70 | 71 | https://github.com/kschingiz/meteor-elastic-apm/blob/master/assets/ 72 | 73 | ## Kibana APM with Meteor with MUP 74 | 75 | Meteor Up is a production quality Meteor app deployment tool. We expect you already has up and running Meteor app on server deployed with MUP. 76 | 77 | 1. `mup ssh` 78 | 2. `wget https://raw.githubusercontent.com/elastic/apm-server/master/apm-server.yml && cp apm-server.yml /etc/apm-server/apm-server.yml` 79 | 3. Now you need to edit /etc/apm-server/apm-server.yml, at least you need to add you elastic search url under `output.elasticsearch`. When you finish just close this terminal 80 | 4. Now we need to update mup.js file to: 81 | a) Install apm-server in app container 82 | b) Pass apm-server config file into our app container 83 | c) Start it everytime after deploy 84 | 85 | ``` 86 | { 87 | app: { 88 | ... 89 | volumes: { 90 | '/etc/apm-server/apm-server.yml': '/etc/apm-server/apm-server.yml' 91 | }, 92 | docker: { 93 | ... 94 | buildInstructions: [ 95 | // https://www.elastic.co/guide/en/apm/server/current/setup-repositories.html 96 | 'RUN apt-get install wget -y', 97 | 'RUN wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | apt-key add -', 98 | 'RUN apt-get install apt-transport-https', 99 | 'RUN echo "deb https://artifacts.elastic.co/packages/6.x/apt stable main" | tee -a /etc/apt/sources.list.d/elastic-6.x.list', 100 | 'RUN apt-get update && apt-get install apm-server -y', 101 | 'RUN update-rc.d apm-server defaults 95 10' 102 | ] 103 | } 104 | ... 105 | }, 106 | ... 107 | hooks: { 108 | // Run apm-server 109 | 'post.deploy'(api) { 110 | return api.runSSHCommand( 111 | api.getConfig().servers.one, 112 | 'docker exec development service apm-server start' 113 | ); 114 | } 115 | }, 116 | } 117 | ``` 118 | 119 | ## demo app 120 | 121 | https://github.com/kschingiz/demo-meteor-elastic-apm 122 | 123 | ## API 124 | 125 | Agent is based on `elastic/apm-agent-nodejs` and fully supports all of it's features https://github.com/elastic/apm-agent-nodejs 126 | 127 | ## Contributions 128 | 129 | All contributions are welcome, Let's make the better APM together! 130 | -------------------------------------------------------------------------------- /instrumenting/db.js: -------------------------------------------------------------------------------- 1 | import shimmer from 'shimmer'; 2 | import { DB } from '../constants'; 3 | 4 | function start(agent, Meteor, MongoCursor) { 5 | const meteorCollectionProto = Meteor.Collection.prototype; 6 | ['findOne', 'find', 'update', 'remove', 'insert', 'createIndex', '_dropIndex'].forEach(function ( 7 | func 8 | ) { 9 | shimmer.wrap(meteorCollectionProto, func, function (original) { 10 | return function (...args) { 11 | const collName = this._name; 12 | const dbExecSpan = agent.startSpan(`${collName}.${func}`, DB); 13 | 14 | function closeSpan(exception, result) { 15 | if (!dbExecSpan) { 16 | return; 17 | } 18 | 19 | if (exception) { 20 | dbExecSpan.addLabels({ 21 | status: 'fail', 22 | exception, 23 | }); 24 | 25 | agent.captureError(exception); 26 | } 27 | 28 | if (func === 'insert') { 29 | const [document] = args; 30 | 31 | dbExecSpan.addLabels({ 32 | document: JSON.stringify(document), 33 | docInserted: result, 34 | }); 35 | } else if (func === 'find' || func === 'findOne') { 36 | const [selector = {}, options = {}] = args; 37 | 38 | let docsFetched = Array.isArray(result) ? result.length : 0; 39 | 40 | if (func === 'findOne') { 41 | docsFetched = result ? 1 : 0; 42 | } 43 | 44 | dbExecSpan.addLabels({ 45 | selector: JSON.stringify(selector), 46 | options: JSON.stringify(options), 47 | docsFetched, 48 | }); 49 | } else if (func === 'update') { 50 | const [selector = {}, modifier = {}, options = {}] = args; 51 | 52 | dbExecSpan.addLabels({ 53 | selector: JSON.stringify(selector), 54 | options: JSON.stringify(options), 55 | modifier: JSON.stringify(modifier), 56 | docsUpdated: result, 57 | }); 58 | } else if (func === 'remove') { 59 | const [selector = {}] = args; 60 | 61 | dbExecSpan.addLabels({ 62 | selector: JSON.stringify(selector), 63 | docsRemoved: result, 64 | }); 65 | } 66 | 67 | dbExecSpan.end(); 68 | } 69 | 70 | try { 71 | if (typeof args[args.length - 1] === 'function') { 72 | const newArgs = args.slice(0, args.length - 1); 73 | const callback = args[args.length - 1]; 74 | 75 | if (dbExecSpan) { 76 | dbExecSpan.addLabels({ 77 | async: true, 78 | }); 79 | } 80 | 81 | const newCallback = (exception, result) => { 82 | closeSpan(exception, result); 83 | 84 | callback(exception, result); 85 | }; 86 | 87 | return original.apply(this, [...newArgs, newCallback]); 88 | } 89 | const ret = original.apply(this, args); 90 | 91 | closeSpan(null, ret); 92 | return ret; 93 | } catch (ex) { 94 | closeSpan(ex); 95 | 96 | throw ex; 97 | } 98 | }; 99 | }); 100 | }); 101 | 102 | const cursorProto = MongoCursor.prototype; 103 | ['forEach', 'map', 'fetch', 'count', 'observeChanges', 'observe', 'rewind'].forEach(function ( 104 | type 105 | ) { 106 | shimmer.wrap(cursorProto, type, function (original) { 107 | return function (...args) { 108 | const cursorDescription = this._cursorDescription; 109 | 110 | const transaction = agent.currentTransaction; 111 | if (transaction) { 112 | if (transaction.__span) { 113 | transaction.__span.end(); 114 | } 115 | transaction.__span = agent.startSpan(`${cursorDescription.collectionName}:${type}`, DB); 116 | } 117 | 118 | function closeSpan(ex, result) { 119 | if (transaction) { 120 | const cursorSpan = transaction.__span; 121 | if (cursorSpan) { 122 | if (ex) { 123 | transaction.__span.addLabels({ 124 | status: 'fail', 125 | exception: ex, 126 | }); 127 | } 128 | 129 | if (type === 'fetch' || type === 'map') { 130 | const docsFetched = result ? result.length : 0; 131 | 132 | cursorSpan.addLabels({ 133 | docsFetched, 134 | }); 135 | } 136 | 137 | cursorSpan.addLabels({ 138 | selector: JSON.stringify(cursorDescription.selector), 139 | }); 140 | 141 | if (cursorDescription.options) { 142 | const { fields, sort, limit } = cursorDescription.options; 143 | 144 | cursorSpan.addLabels({ 145 | fields: JSON.stringify(fields || {}), 146 | sort: JSON.stringify(sort || {}), 147 | limit, 148 | }); 149 | } 150 | 151 | cursorSpan.end(); 152 | transaction.__span = undefined; 153 | } 154 | } 155 | if (ex) { 156 | agent.captureError(ex); 157 | } 158 | } 159 | 160 | try { 161 | const result = original.apply(this, args); 162 | 163 | closeSpan(null, result); 164 | return result; 165 | } catch (ex) { 166 | closeSpan(ex); 167 | throw ex; 168 | } 169 | }; 170 | }); 171 | }); 172 | } 173 | 174 | module.exports = start; 175 | -------------------------------------------------------------------------------- /__tests__/sessionInstrument.test.js: -------------------------------------------------------------------------------- 1 | const instrumentSession = require('../instrumenting/session'); 2 | const newAgent = require('./mocks/agent'); 3 | const newSession = require('./mocks/session'); 4 | 5 | test('track session method messages', () => { 6 | const Session = newSession(); 7 | 8 | const agent = newAgent(); 9 | agent.currentTransaction = { 10 | name: 'test' 11 | }; 12 | 13 | instrumentSession(agent, Session); 14 | 15 | const clientSession = new Session(); 16 | 17 | const msg = { 18 | msg: 'method', 19 | method: 'methodCall' 20 | }; 21 | 22 | clientSession.processMessage(msg); 23 | 24 | expect(agent.startTransaction.mock.calls.length).toBe(1); 25 | expect(agent.startTransaction.mock.calls[0][0]).toBe('methodCall'); 26 | expect(agent.startTransaction.mock.calls[0][1]).toBe('method'); 27 | }); 28 | 29 | test('track session sub messages', () => { 30 | const Session = newSession(); 31 | 32 | const agent = newAgent(); 33 | agent.currentTransaction = { 34 | name: 'test' 35 | }; 36 | 37 | instrumentSession(agent, Session); 38 | 39 | const clientSession = new Session(); 40 | 41 | const msg = { 42 | msg: 'sub', 43 | name: 'subName' 44 | }; 45 | 46 | clientSession.processMessage(msg); 47 | 48 | expect(agent.startTransaction.mock.calls.length).toBe(1); 49 | expect(agent.startTransaction.mock.calls[0][0]).toBe('subName'); 50 | expect(agent.startTransaction.mock.calls[0][1]).toBe('sub'); 51 | }); 52 | 53 | test('ignore session message is not method and sub', () => { 54 | const Session = newSession(); 55 | 56 | const agent = newAgent(); 57 | agent.currentTransaction = { 58 | name: 'test' 59 | }; 60 | 61 | instrumentSession(agent, Session); 62 | 63 | const clientSession = new Session(); 64 | 65 | const msg = { 66 | msg: 'ping' 67 | }; 68 | 69 | clientSession.processMessage(msg); 70 | 71 | expect(agent.startTransaction.mock.calls.length).toBe(0); 72 | }); 73 | 74 | test('session meteor method call', () => { 75 | const Session = newSession(); 76 | 77 | const agent = newAgent(); 78 | agent.currentTransaction = { 79 | name: 'test' 80 | }; 81 | 82 | instrumentSession(agent, Session); 83 | 84 | const clientSession = new Session(); 85 | 86 | const transaction = agent.startTransaction(); 87 | const msg = { 88 | msg: 'sub', 89 | name: 'subName', 90 | __transaction: transaction 91 | }; 92 | 93 | clientSession.protocol_handlers.method(msg); 94 | 95 | expect(agent.startSpan.mock.calls.length).toBe(1); 96 | expect(agent.startSpan.mock.calls[0][0]).toBe('execution'); 97 | }); 98 | 99 | test('session meteor method call with waitSpan', () => { 100 | const Session = newSession(); 101 | 102 | const agent = newAgent(); 103 | agent.currentTransaction = { 104 | name: 'test' 105 | }; 106 | 107 | instrumentSession(agent, Session); 108 | 109 | const clientSession = new Session(); 110 | 111 | const transaction = agent.startTransaction(); 112 | const waitSpan = agent.startSpan(); 113 | 114 | const msg = { 115 | msg: 'sub', 116 | name: 'subName', 117 | __transaction: transaction 118 | }; 119 | 120 | msg.__transaction.__span = waitSpan; 121 | 122 | clientSession.protocol_handlers.method(msg); 123 | 124 | expect(agent.startSpan.mock.calls.length).toBe(2); 125 | expect(agent.startSpan.mock.calls[1][0]).toBe('execution'); 126 | 127 | expect(waitSpan.end.mock.calls.length).toBe(1); 128 | }); 129 | 130 | test('ignore meteor method call if transaction does not exist', () => { 131 | const Session = newSession(); 132 | 133 | const agent = newAgent(); 134 | 135 | instrumentSession(agent, Session); 136 | 137 | const clientSession = new Session(); 138 | 139 | const msg = { 140 | msg: 'sub', 141 | name: 'subName' 142 | }; 143 | 144 | clientSession.protocol_handlers.method(msg); 145 | 146 | expect(agent.startSpan.mock.calls.length).toBe(0); 147 | }); 148 | 149 | test('session sub call', () => { 150 | const Session = newSession(); 151 | 152 | const agent = newAgent(); 153 | agent.currentTransaction = { 154 | name: 'test' 155 | }; 156 | 157 | instrumentSession(agent, Session); 158 | 159 | const clientSession = new Session(); 160 | 161 | const transaction = agent.startTransaction(); 162 | const msg = { 163 | msg: 'sub', 164 | name: 'subName', 165 | __transaction: transaction 166 | }; 167 | 168 | clientSession.protocol_handlers.sub(msg); 169 | 170 | expect(agent.startSpan.mock.calls.length).toBe(1); 171 | expect(agent.startSpan.mock.calls[0][0]).toBe('execution'); 172 | }); 173 | 174 | test('session sub call with waitSpan', () => { 175 | const Session = newSession(); 176 | 177 | const agent = newAgent(); 178 | agent.currentTransaction = { 179 | name: 'test' 180 | }; 181 | 182 | instrumentSession(agent, Session); 183 | 184 | const clientSession = new Session(); 185 | 186 | const transaction = agent.startTransaction(); 187 | const waitSpan = agent.startSpan(); 188 | const msg = { 189 | msg: 'sub', 190 | name: 'subName', 191 | __transaction: transaction 192 | }; 193 | 194 | msg.__transaction.__span = waitSpan; 195 | 196 | clientSession.protocol_handlers.sub(msg); 197 | 198 | expect(agent.startSpan.mock.calls.length).toBe(2); 199 | expect(agent.startSpan.mock.calls[1][0]).toBe('execution'); 200 | 201 | expect(waitSpan.end.mock.calls.length).toBe(1); 202 | }); 203 | 204 | test('session sub call if transaction does not exist', () => { 205 | const Session = newSession(); 206 | 207 | const agent = newAgent(); 208 | 209 | instrumentSession(agent, Session); 210 | 211 | const clientSession = new Session(); 212 | 213 | const msg = { 214 | msg: 'sub', 215 | name: 'subName' 216 | }; 217 | 218 | clientSession.protocol_handlers.sub(msg); 219 | 220 | expect(agent.startSpan.mock.calls.length).toBe(0); 221 | }); 222 | 223 | test('session unsub call', () => { 224 | const Session = newSession(); 225 | 226 | const agent = newAgent(); 227 | agent.currentTransaction = { 228 | name: 'test' 229 | }; 230 | 231 | instrumentSession(agent, Session); 232 | 233 | const clientSession = new Session(); 234 | 235 | const transaction = agent.startTransaction(); 236 | const msg = { 237 | msg: 'sub', 238 | name: 'subName', 239 | __transaction: transaction 240 | }; 241 | 242 | clientSession.protocol_handlers.unsub(msg); 243 | 244 | expect(agent.startSpan.mock.calls.length).toBe(1); 245 | expect(agent.startSpan.mock.calls[0][0]).toBe('execution'); 246 | 247 | expect(msg.__transaction.end.mock.calls.length).toBe(1); 248 | }); 249 | 250 | test('session unsub call with waitSpan', () => { 251 | const Session = newSession(); 252 | 253 | const agent = newAgent(); 254 | agent.currentTransaction = { 255 | name: 'test' 256 | }; 257 | 258 | instrumentSession(agent, Session); 259 | 260 | const clientSession = new Session(); 261 | 262 | const transaction = agent.startTransaction(); 263 | const waitSpan = agent.startSpan(); 264 | const msg = { 265 | msg: 'sub', 266 | name: 'subName', 267 | __transaction: transaction 268 | }; 269 | 270 | msg.__transaction.__span = waitSpan; 271 | 272 | clientSession.protocol_handlers.unsub(msg); 273 | 274 | expect(agent.startSpan.mock.calls.length).toBe(2); 275 | expect(agent.startSpan.mock.calls[1][0]).toBe('execution'); 276 | 277 | expect(waitSpan.end.mock.calls.length).toBe(1); 278 | expect(msg.__transaction.end.mock.calls.length).toBe(1); 279 | }); 280 | 281 | test('ignore session unsub call if transaction does not exist', () => { 282 | const Session = newSession(); 283 | 284 | const agent = newAgent(); 285 | 286 | instrumentSession(agent, Session); 287 | 288 | const clientSession = new Session(); 289 | 290 | const msg = { 291 | msg: 'sub', 292 | name: 'subName' 293 | }; 294 | 295 | clientSession.protocol_handlers.unsub(msg); 296 | 297 | expect(agent.startSpan.mock.calls.length).toBe(0); 298 | }); 299 | -------------------------------------------------------------------------------- /__tests__/dbInstrument.test.js: -------------------------------------------------------------------------------- 1 | const instrumentDB = require('../instrumenting/db'); 2 | const newAgent = require('./mocks/agent'); 3 | const newMeteor = require('./mocks/meteor'); 4 | const newMongoCursor = require('./mocks/mongoCursor'); 5 | 6 | test('track meteor collection methods (fibers)', () => { 7 | const agent = newAgent(); 8 | const Meteor = newMeteor(); 9 | const MongoCursor = newMongoCursor(); 10 | 11 | instrumentDB(agent, Meteor, MongoCursor); 12 | 13 | agent.currentTransaction = agent.startTransaction(); 14 | 15 | const testCollection = new Meteor.Collection(); 16 | testCollection._name = 'testCollection'; 17 | 18 | testCollection.find({ _id: 1 }); 19 | 20 | expect(agent.startSpan.mock.calls.length).toBe(1); 21 | expect(agent.startSpan.mock.calls[0][0]).toBe(`${testCollection._name}.find`); 22 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 23 | }); 24 | 25 | test('track meteor collection methods (callback)', () => { 26 | const agent = newAgent(); 27 | const Meteor = newMeteor(); 28 | const MongoCursor = newMongoCursor(); 29 | 30 | Meteor.Collection.prototype.insert = function(data, callback) { 31 | setTimeout(() => callback(), 100); 32 | }; 33 | 34 | instrumentDB(agent, Meteor, MongoCursor); 35 | 36 | agent.currentTransaction = agent.startTransaction(); 37 | 38 | const testCollection = new Meteor.Collection(); 39 | testCollection._name = 'testCollection'; 40 | 41 | return new Promise((res, rej) => { 42 | testCollection.insert({ _id: 1 }, function(err) { 43 | if (err) { 44 | rej(err); 45 | } else { 46 | res( 47 | Promise.resolve().then(() => { 48 | expect(agent.startSpan.mock.calls.length).toBe(1); 49 | expect(agent.startSpan.mock.calls[0][0]).toBe(`${testCollection._name}.insert`); 50 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 51 | }) 52 | ); 53 | } 54 | }); 55 | }); 56 | }); 57 | 58 | test('catch collection exceptions', () => { 59 | const agent = newAgent(); 60 | const Meteor = newMeteor(); 61 | const MongoCursor = newMongoCursor(); 62 | 63 | Meteor.Collection.prototype.find = () => { 64 | throw new Error(); 65 | }; 66 | 67 | instrumentDB(agent, Meteor, MongoCursor); 68 | 69 | const testCollection = new Meteor.Collection(); 70 | 71 | agent.currentTransaction = agent.startTransaction(); 72 | testCollection._name = 'testCollection'; 73 | 74 | expect(() => { 75 | testCollection.find({ _id: 1 }); 76 | }).toThrow(); 77 | 78 | expect(agent.startSpan.mock.calls.length).toBe(1); 79 | expect(agent.startSpan.mock.calls[0][0]).toBe(`${testCollection._name}.find`); 80 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 81 | expect(agent.captureError.mock.calls.length).toBe(1); 82 | 83 | Meteor.Collection.prototype.find = jest.fn(); 84 | }); 85 | 86 | test('track collection insert', () => { 87 | const agent = newAgent(); 88 | const Meteor = newMeteor(); 89 | const MongoCursor = newMongoCursor(); 90 | 91 | instrumentDB(agent, Meteor, MongoCursor); 92 | 93 | agent.currentTransaction = agent.startTransaction(); 94 | 95 | const testCollection = new Meteor.Collection(); 96 | testCollection._name = 'testCollection'; 97 | 98 | testCollection.insert({ _id: 1 }); 99 | 100 | expect(agent.startSpan.mock.calls.length).toBe(1); 101 | expect(agent.startSpan.mock.calls[0][0]).toBe(`${testCollection._name}.insert`); 102 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 103 | }); 104 | 105 | test('track collection update', () => { 106 | const agent = newAgent(); 107 | const Meteor = newMeteor(); 108 | const MongoCursor = newMongoCursor(); 109 | 110 | instrumentDB(agent, Meteor, MongoCursor); 111 | 112 | agent.currentTransaction = agent.startTransaction(); 113 | 114 | const testCollection = new Meteor.Collection(); 115 | testCollection._name = 'testCollection'; 116 | 117 | testCollection.update({ _id: 1 }, { $set: { prop: 1 } }); 118 | 119 | expect(agent.startSpan.mock.calls.length).toBe(1); 120 | expect(agent.startSpan.mock.calls[0][0]).toBe(`${testCollection._name}.update`); 121 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 122 | }); 123 | 124 | test('track collection remove', () => { 125 | const agent = newAgent(); 126 | const Meteor = newMeteor(); 127 | const MongoCursor = newMongoCursor(); 128 | 129 | instrumentDB(agent, Meteor, MongoCursor); 130 | 131 | agent.currentTransaction = agent.startTransaction(); 132 | 133 | const testCollection = new Meteor.Collection(); 134 | testCollection._name = 'testCollection'; 135 | 136 | testCollection.remove({ _id: 1 }); 137 | 138 | expect(agent.startSpan.mock.calls.length).toBe(1); 139 | expect(agent.startSpan.mock.calls[0][0]).toBe(`${testCollection._name}.remove`); 140 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 141 | }); 142 | 143 | test('track collection findOne', () => { 144 | const agent = newAgent(); 145 | const Meteor = newMeteor(); 146 | const MongoCursor = newMongoCursor(); 147 | 148 | instrumentDB(agent, Meteor, MongoCursor); 149 | 150 | agent.currentTransaction = agent.startTransaction(); 151 | 152 | const testCollection = new Meteor.Collection(); 153 | testCollection._name = 'testCollection'; 154 | 155 | testCollection.findOne({ _id: 1 }); 156 | 157 | expect(agent.startSpan.mock.calls.length).toBe(1); 158 | expect(agent.startSpan.mock.calls[0][0]).toBe(`${testCollection._name}.findOne`); 159 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 160 | }); 161 | 162 | test('track mongo cursor methods', () => { 163 | const agent = newAgent(); 164 | const Meteor = newMeteor(); 165 | const MongoCursor = newMongoCursor(); 166 | 167 | instrumentDB(agent, Meteor, MongoCursor); 168 | 169 | agent.currentTransaction = agent.startTransaction(); 170 | 171 | const cursor = new MongoCursor(); 172 | 173 | cursor._cursorDescription = { 174 | collectionName: 'test' 175 | }; 176 | 177 | cursor.count(); 178 | 179 | expect(agent.startSpan.mock.calls.length).toBe(1); 180 | expect(agent.startSpan.mock.calls[0][0]).toBe('test:count'); 181 | expect(agent.startSpan.mock.calls[0][1]).toBe('db'); 182 | }); 183 | 184 | test('close transaction and its span on cursor methods', () => { 185 | const agent = newAgent(); 186 | const Meteor = newMeteor(); 187 | const MongoCursor = newMongoCursor(); 188 | 189 | instrumentDB(agent, Meteor, MongoCursor); 190 | 191 | agent.currentTransaction = agent.startTransaction(); 192 | const span = agent.startSpan(); 193 | agent.currentTransaction.__span = span; 194 | 195 | const cursor = new MongoCursor(); 196 | 197 | cursor._cursorDescription = { 198 | collectionName: 'test' 199 | }; 200 | 201 | cursor.count(); 202 | 203 | expect(agent.startSpan.mock.calls.length).toBe(2); 204 | expect(agent.startSpan.mock.calls[1][0]).toBe('test:count'); 205 | expect(agent.startSpan.mock.calls[1][1]).toBe('db'); 206 | expect(span.end.mock.calls.length).toBe(1); 207 | }); 208 | 209 | test('track db cursor fetch and map methods', () => { 210 | const agent = newAgent(); 211 | const Meteor = newMeteor(); 212 | const MongoCursor = newMongoCursor(); 213 | 214 | instrumentDB(agent, Meteor, MongoCursor); 215 | 216 | agent.currentTransaction = agent.startTransaction(); 217 | const span = agent.startSpan(); 218 | agent.currentTransaction.__span = span; 219 | 220 | const cursor = new MongoCursor(); 221 | 222 | cursor._cursorDescription = { 223 | collectionName: 'test' 224 | }; 225 | 226 | cursor.fetch(); 227 | 228 | expect(agent.startSpan.mock.calls.length).toBe(2); 229 | expect(agent.startSpan.mock.calls[1][0]).toBe('test:fetch'); 230 | expect(agent.startSpan.mock.calls[1][1]).toBe('db'); 231 | expect(span.end.mock.calls.length).toBe(1); 232 | }); 233 | 234 | test('track db cursor options', () => { 235 | const agent = newAgent(); 236 | const Meteor = newMeteor(); 237 | const MongoCursor = newMongoCursor(); 238 | 239 | instrumentDB(agent, Meteor, MongoCursor); 240 | 241 | agent.currentTransaction = agent.startTransaction(); 242 | const span = agent.startSpan(); 243 | agent.currentTransaction.__span = span; 244 | 245 | const cursor = new MongoCursor(); 246 | 247 | cursor._cursorDescription = { 248 | collectionName: 'test', 249 | options: { 250 | fields: { field1: 1 }, 251 | sort: { field1: 1 }, 252 | limit: 1 253 | } 254 | }; 255 | 256 | cursor.fetch(); 257 | 258 | expect(agent.startSpan.mock.calls.length).toBe(2); 259 | expect(agent.startSpan.mock.calls[1][0]).toBe('test:fetch'); 260 | expect(agent.startSpan.mock.calls[1][1]).toBe('db'); 261 | expect(span.end.mock.calls.length).toBe(1); 262 | }); 263 | 264 | test('ignore cursor method if transaction is undefined', () => { 265 | const agent = newAgent(); 266 | const Meteor = newMeteor(); 267 | const MongoCursor = newMongoCursor(); 268 | 269 | instrumentDB(agent, Meteor, MongoCursor); 270 | 271 | const cursor = new MongoCursor(); 272 | 273 | cursor.fetch(); 274 | 275 | expect(agent.startSpan.mock.calls.length).toBe(0); 276 | }); 277 | 278 | test('catch cursor exceptions', () => { 279 | const agent = newAgent(); 280 | const Meteor = newMeteor(); 281 | const MongoCursor = newMongoCursor(); 282 | 283 | MongoCursor.prototype.fetch = () => { 284 | throw new Error(); 285 | }; 286 | 287 | instrumentDB(agent, Meteor, MongoCursor); 288 | 289 | agent.currentTransaction = agent.startTransaction(); 290 | const span = agent.startSpan(); 291 | agent.currentTransaction.__span = span; 292 | 293 | const cursor = new MongoCursor(); 294 | 295 | cursor._cursorDescription = { 296 | collectionName: 'test', 297 | options: { 298 | fields: { field1: 1 }, 299 | sort: { field1: 1 }, 300 | limit: 1 301 | } 302 | }; 303 | 304 | expect(() => { 305 | cursor.fetch(); 306 | }).toThrow(); 307 | 308 | expect(agent.startSpan.mock.calls.length).toBe(2); 309 | expect(agent.startSpan.mock.calls[1][0]).toBe('test:fetch'); 310 | expect(agent.startSpan.mock.calls[1][1]).toBe('db'); 311 | expect(span.end.mock.calls.length).toBe(1); 312 | expect(agent.captureError.mock.calls.length).toBe(1); 313 | 314 | MongoCursor.prototype.fetch = jest.fn(); 315 | }); 316 | -------------------------------------------------------------------------------- /.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "dependencies": { 4 | "@babel/code-frame": { 5 | "version": "7.18.6", 6 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", 7 | "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" 8 | }, 9 | "@babel/helper-validator-identifier": { 10 | "version": "7.19.1", 11 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", 12 | "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" 13 | }, 14 | "@babel/highlight": { 15 | "version": "7.18.6", 16 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 17 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" 18 | }, 19 | "@elastic/ecs-helpers": { 20 | "version": "1.1.0", 21 | "resolved": "https://registry.npmjs.org/@elastic/ecs-helpers/-/ecs-helpers-1.1.0.tgz", 22 | "integrity": "sha512-MDLb2aFeGjg46O5mLpdCzT5yOUDnXToJSrco2ShqGIXxNJaM8uJjX+4nd+hRYV4Vex8YJyDtOFEVBldQct6ndg==" 23 | }, 24 | "@elastic/ecs-pino-format": { 25 | "version": "1.3.0", 26 | "resolved": "https://registry.npmjs.org/@elastic/ecs-pino-format/-/ecs-pino-format-1.3.0.tgz", 27 | "integrity": "sha512-U8D57gPECYoRCcwREsrXKBtqeyFFF/KAwHi4rG1u/oQhAg91Kzw8ZtUQJXD/DMDieLOqtbItFr2FRBWI3t3wog==" 28 | }, 29 | "@types/normalize-package-data": { 30 | "version": "2.4.1", 31 | "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", 32 | "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" 33 | }, 34 | "after-all-results": { 35 | "version": "2.0.0", 36 | "resolved": "https://registry.npmjs.org/after-all-results/-/after-all-results-2.0.0.tgz", 37 | "integrity": "sha512-2zHEyuhSJOuCrmas9YV0YL/MFCWLxe1dS6k/ENhgYrb/JqyMnadLN4iIAc9kkZrbElMDyyAGH/0J18OPErOWLg==" 38 | }, 39 | "ajv": { 40 | "version": "6.12.6", 41 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 42 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==" 43 | }, 44 | "ansi-styles": { 45 | "version": "3.2.1", 46 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 47 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" 48 | }, 49 | "async-cache": { 50 | "version": "1.1.0", 51 | "resolved": "https://registry.npmjs.org/async-cache/-/async-cache-1.1.0.tgz", 52 | "integrity": "sha512-YDQc4vBn5NFhY6g6HhVshyi3Fy9+SQ5ePnE7JLDJn1DoL+i7ER+vMwtTNOYk9leZkYMnOwpBCWqyLDPw8Aig8g==" 53 | }, 54 | "async-value": { 55 | "version": "1.2.2", 56 | "resolved": "https://registry.npmjs.org/async-value/-/async-value-1.2.2.tgz", 57 | "integrity": "sha512-8rwtYe32OAS1W9CTwvknoyts+mc3ta8N7Pi0h7AjkMaKvsFbr39K+gEfZ7Z81aPXQ1sK5M23lgLy1QfZpcpadQ==" 58 | }, 59 | "async-value-promise": { 60 | "version": "1.1.1", 61 | "resolved": "https://registry.npmjs.org/async-value-promise/-/async-value-promise-1.1.1.tgz", 62 | "integrity": "sha512-c2RFDKjJle1rHa0YxN9Ysu97/QBu3Wa+NOejJxsX+1qVDJrkD3JL/GN1B3gaILAEXJXbu/4Z1lcoCHFESe/APA==" 63 | }, 64 | "atomic-sleep": { 65 | "version": "1.0.0", 66 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 67 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" 68 | }, 69 | "available-typed-arrays": { 70 | "version": "1.0.5", 71 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", 72 | "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" 73 | }, 74 | "basic-auth": { 75 | "version": "2.0.1", 76 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 77 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==" 78 | }, 79 | "binary-search": { 80 | "version": "1.3.6", 81 | "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", 82 | "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" 83 | }, 84 | "breadth-filter": { 85 | "version": "2.0.0", 86 | "resolved": "https://registry.npmjs.org/breadth-filter/-/breadth-filter-2.0.0.tgz", 87 | "integrity": "sha512-thQShDXnFWSk2oVBixRCyrWsFoV5tfOpWKHmxwafHQDNxCfDBk539utpvytNjmlFrTMqz41poLwJvA1MW3z0MQ==" 88 | }, 89 | "call-bind": { 90 | "version": "1.0.2", 91 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 92 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" 93 | }, 94 | "chalk": { 95 | "version": "2.4.2", 96 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 97 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 98 | "dependencies": { 99 | "escape-string-regexp": { 100 | "version": "1.0.5", 101 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 102 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" 103 | } 104 | } 105 | }, 106 | "color-convert": { 107 | "version": "1.9.3", 108 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 109 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" 110 | }, 111 | "color-name": { 112 | "version": "1.1.3", 113 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 114 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 115 | }, 116 | "console-log-level": { 117 | "version": "1.4.1", 118 | "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", 119 | "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" 120 | }, 121 | "container-info": { 122 | "version": "1.1.0", 123 | "resolved": "https://registry.npmjs.org/container-info/-/container-info-1.1.0.tgz", 124 | "integrity": "sha512-eD2zLAmxGS2kmL4f1jY8BdOqnmpL6X70kvzTBW/9FIQnxoxiBJ4htMsTmtPLPWRs7NHYFvqKQ1VtppV08mdsQA==" 125 | }, 126 | "cookie": { 127 | "version": "0.4.2", 128 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", 129 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" 130 | }, 131 | "core-util-is": { 132 | "version": "1.0.3", 133 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 134 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 135 | }, 136 | "debug": { 137 | "version": "4.3.4", 138 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 139 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" 140 | }, 141 | "deepmerge": { 142 | "version": "4.2.2", 143 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 144 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" 145 | }, 146 | "define-properties": { 147 | "version": "1.1.4", 148 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", 149 | "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==" 150 | }, 151 | "elastic-apm-http-client": { 152 | "version": "9.9.0", 153 | "resolved": "https://registry.npmjs.org/elastic-apm-http-client/-/elastic-apm-http-client-9.9.0.tgz", 154 | "integrity": "sha512-6AXlZtGoo1B9qZMNw51vU9CmJa0G37fUZV5HXPF58kfhztWiYW2RwZCn8Kjx+EUk9eV6wAGms4rqw9DLROZxzw==" 155 | }, 156 | "elastic-apm-node": { 157 | "version": "3.15.0", 158 | "resolved": "https://registry.npmjs.org/elastic-apm-node/-/elastic-apm-node-3.15.0.tgz", 159 | "integrity": "sha512-h5uHnMiWIvZiqKH/4Q58yp73qJkUdcIBdK3HG0dcAfUHvx50tzUPuNbvgxv8YeZjnc59kIxVUR68tBOJw+5kDA==" 160 | }, 161 | "end-of-stream": { 162 | "version": "1.4.4", 163 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 164 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==" 165 | }, 166 | "error-callsites": { 167 | "version": "2.0.4", 168 | "resolved": "https://registry.npmjs.org/error-callsites/-/error-callsites-2.0.4.tgz", 169 | "integrity": "sha512-V877Ch4FC4FN178fDK1fsrHN4I1YQIBdtjKrHh3BUHMnh3SMvwUVrqkaOgDpUuevgSNna0RBq6Ox9SGlxYrigA==" 170 | }, 171 | "error-ex": { 172 | "version": "1.3.2", 173 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 174 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==" 175 | }, 176 | "error-stack-parser": { 177 | "version": "2.1.4", 178 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", 179 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==" 180 | }, 181 | "es-abstract": { 182 | "version": "1.21.1", 183 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", 184 | "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==" 185 | }, 186 | "es-set-tostringtag": { 187 | "version": "2.0.1", 188 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", 189 | "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==" 190 | }, 191 | "es-to-primitive": { 192 | "version": "1.2.1", 193 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 194 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==" 195 | }, 196 | "escape-string-regexp": { 197 | "version": "4.0.0", 198 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 199 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" 200 | }, 201 | "fast-deep-equal": { 202 | "version": "3.1.3", 203 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 204 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 205 | }, 206 | "fast-json-stable-stringify": { 207 | "version": "2.1.0", 208 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 209 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 210 | }, 211 | "fast-json-stringify": { 212 | "version": "2.7.13", 213 | "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz", 214 | "integrity": "sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA==" 215 | }, 216 | "fast-redact": { 217 | "version": "3.1.2", 218 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", 219 | "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==" 220 | }, 221 | "fast-safe-stringify": { 222 | "version": "2.1.1", 223 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", 224 | "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" 225 | }, 226 | "fast-stream-to-buffer": { 227 | "version": "1.0.0", 228 | "resolved": "https://registry.npmjs.org/fast-stream-to-buffer/-/fast-stream-to-buffer-1.0.0.tgz", 229 | "integrity": "sha512-bI/544WUQlD2iXBibQbOMSmG07Hay7YrpXlKaeGTPT7H7pC0eitt3usak5vUwEvCGK/O7rUAM3iyQValGU22TQ==" 230 | }, 231 | "find-up": { 232 | "version": "4.1.0", 233 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 234 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==" 235 | }, 236 | "flatstr": { 237 | "version": "1.0.12", 238 | "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", 239 | "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" 240 | }, 241 | "for-each": { 242 | "version": "0.3.3", 243 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 244 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==" 245 | }, 246 | "forwarded-parse": { 247 | "version": "2.1.2", 248 | "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", 249 | "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==" 250 | }, 251 | "function-bind": { 252 | "version": "1.1.1", 253 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 254 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 255 | }, 256 | "function.prototype.name": { 257 | "version": "1.1.5", 258 | "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", 259 | "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==" 260 | }, 261 | "functions-have-names": { 262 | "version": "1.2.3", 263 | "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", 264 | "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" 265 | }, 266 | "get-intrinsic": { 267 | "version": "1.2.0", 268 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 269 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==" 270 | }, 271 | "get-symbol-description": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", 274 | "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==" 275 | }, 276 | "globalthis": { 277 | "version": "1.0.3", 278 | "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", 279 | "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==" 280 | }, 281 | "gopd": { 282 | "version": "1.0.1", 283 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 284 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==" 285 | }, 286 | "has": { 287 | "version": "1.0.3", 288 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 289 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" 290 | }, 291 | "has-bigints": { 292 | "version": "1.0.2", 293 | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", 294 | "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" 295 | }, 296 | "has-flag": { 297 | "version": "3.0.0", 298 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 299 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" 300 | }, 301 | "has-property-descriptors": { 302 | "version": "1.0.0", 303 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", 304 | "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==" 305 | }, 306 | "has-proto": { 307 | "version": "1.0.1", 308 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 309 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" 310 | }, 311 | "has-symbols": { 312 | "version": "1.0.3", 313 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 314 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 315 | }, 316 | "has-tostringtag": { 317 | "version": "1.0.0", 318 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", 319 | "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==" 320 | }, 321 | "hosted-git-info": { 322 | "version": "2.8.9", 323 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 324 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" 325 | }, 326 | "http-headers": { 327 | "version": "3.0.2", 328 | "resolved": "https://registry.npmjs.org/http-headers/-/http-headers-3.0.2.tgz", 329 | "integrity": "sha512-87E1I+2Wg4dxxz4rcxElo3dxO/w1ZtgL1yA0Sb6vH3qU16vRKq1NjWQv9SCY3ly2OQROcoxHZOUpmelS+k6wOw==" 330 | }, 331 | "in-publish": { 332 | "version": "2.0.1", 333 | "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", 334 | "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==" 335 | }, 336 | "inherits": { 337 | "version": "2.0.4", 338 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 339 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 340 | }, 341 | "internal-slot": { 342 | "version": "1.0.4", 343 | "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", 344 | "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==" 345 | }, 346 | "is-array-buffer": { 347 | "version": "3.0.1", 348 | "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", 349 | "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==" 350 | }, 351 | "is-arrayish": { 352 | "version": "0.2.1", 353 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 354 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" 355 | }, 356 | "is-bigint": { 357 | "version": "1.0.4", 358 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", 359 | "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==" 360 | }, 361 | "is-boolean-object": { 362 | "version": "1.1.2", 363 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", 364 | "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==" 365 | }, 366 | "is-callable": { 367 | "version": "1.2.7", 368 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", 369 | "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" 370 | }, 371 | "is-core-module": { 372 | "version": "2.11.0", 373 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 374 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==" 375 | }, 376 | "is-date-object": { 377 | "version": "1.0.5", 378 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", 379 | "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==" 380 | }, 381 | "is-finite": { 382 | "version": "1.1.0", 383 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", 384 | "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" 385 | }, 386 | "is-integer": { 387 | "version": "1.0.7", 388 | "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.7.tgz", 389 | "integrity": "sha512-RPQc/s9yBHSvpi+hs9dYiJ2cuFeU6x3TyyIp8O2H6SKEltIvJOzRj9ToyvcStDvPR/pS4rxgr1oBFajQjZ2Szg==" 390 | }, 391 | "is-native": { 392 | "version": "1.0.1", 393 | "resolved": "https://registry.npmjs.org/is-native/-/is-native-1.0.1.tgz", 394 | "integrity": "sha512-I4z9hx+4u3/zyvpvGtAR+n7SodJugE+i2jiS8yfq1A9QAZY0KldLQz0SBptLC9ti7kBlpghWUwTKE2BA62eCcw==" 395 | }, 396 | "is-negative-zero": { 397 | "version": "2.0.2", 398 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", 399 | "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" 400 | }, 401 | "is-nil": { 402 | "version": "1.0.1", 403 | "resolved": "https://registry.npmjs.org/is-nil/-/is-nil-1.0.1.tgz", 404 | "integrity": "sha512-m2Rm8PhUFDNNhgvwZJjJG74a9h5CHU0fkA8WT+WGlCjyEbZ2jPwgb+ZxHu4np284EqNVyOsgppReK4qy/TwEwg==" 405 | }, 406 | "is-number-object": { 407 | "version": "1.0.7", 408 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", 409 | "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==" 410 | }, 411 | "is-regex": { 412 | "version": "1.1.4", 413 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", 414 | "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==" 415 | }, 416 | "is-shared-array-buffer": { 417 | "version": "1.0.2", 418 | "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", 419 | "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==" 420 | }, 421 | "is-string": { 422 | "version": "1.0.7", 423 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", 424 | "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==" 425 | }, 426 | "is-symbol": { 427 | "version": "1.0.4", 428 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", 429 | "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==" 430 | }, 431 | "is-typed-array": { 432 | "version": "1.1.10", 433 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", 434 | "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==" 435 | }, 436 | "is-weakref": { 437 | "version": "1.0.2", 438 | "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", 439 | "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==" 440 | }, 441 | "js-tokens": { 442 | "version": "4.0.0", 443 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 444 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 445 | }, 446 | "json-parse-even-better-errors": { 447 | "version": "2.3.1", 448 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 449 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" 450 | }, 451 | "json-schema-traverse": { 452 | "version": "0.4.1", 453 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 454 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 455 | }, 456 | "lines-and-columns": { 457 | "version": "1.2.4", 458 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 459 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" 460 | }, 461 | "load-source-map": { 462 | "version": "1.0.0", 463 | "resolved": "https://registry.npmjs.org/load-source-map/-/load-source-map-1.0.0.tgz", 464 | "integrity": "sha512-U69swlFKzCLHTWBBrN+Skou49GPVn9sg8JkngBFh1A/WzjOPaJ2lgtWSGh+457Qsg+r/gmHHFBwsSJQA7nI6sQ==", 465 | "dependencies": { 466 | "semver": { 467 | "version": "5.7.1", 468 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 469 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 470 | } 471 | } 472 | }, 473 | "locate-path": { 474 | "version": "5.0.0", 475 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 476 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==" 477 | }, 478 | "lru-cache": { 479 | "version": "4.1.5", 480 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", 481 | "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==" 482 | }, 483 | "mapcap": { 484 | "version": "1.0.0", 485 | "resolved": "https://registry.npmjs.org/mapcap/-/mapcap-1.0.0.tgz", 486 | "integrity": "sha512-KcNlZSlFPx+r1jYZmxEbTVymG+dIctf10WmWkuhrhrblM+KMoF77HelwihL5cxYlORye79KoR4IlOOk99lUJ0g==" 487 | }, 488 | "measured-core": { 489 | "version": "1.51.1", 490 | "resolved": "https://registry.npmjs.org/measured-core/-/measured-core-1.51.1.tgz", 491 | "integrity": "sha512-DZQP9SEwdqqYRvT2slMK81D/7xwdxXosZZBtLVfPSo6y5P672FBTbzHVdN4IQyUkUpcVOR9pIvtUy5Ryl7NKyg==" 492 | }, 493 | "measured-reporting": { 494 | "version": "1.51.1", 495 | "resolved": "https://registry.npmjs.org/measured-reporting/-/measured-reporting-1.51.1.tgz", 496 | "integrity": "sha512-JCt+2u6XT1I5lG3SuYqywE0e62DJuAzBcfMzWGUhIYtPQV2Vm4HiYt/durqmzsAbZV181CEs+o/jMKWJKkYIWw==" 497 | }, 498 | "module-details-from-path": { 499 | "version": "1.0.3", 500 | "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", 501 | "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" 502 | }, 503 | "monitor-event-loop-delay": { 504 | "version": "1.0.0", 505 | "resolved": "https://registry.npmjs.org/monitor-event-loop-delay/-/monitor-event-loop-delay-1.0.0.tgz", 506 | "integrity": "sha512-YRIr1exCIfBDLZle8WHOfSo7Xg3M+phcZfq9Fx1L6Abo+atGp7cge5pM7PjyBn4s1oZI/BRD4EMrzQBbPpVb5Q==" 507 | }, 508 | "ms": { 509 | "version": "2.1.2", 510 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 511 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 512 | }, 513 | "next-line": { 514 | "version": "1.1.0", 515 | "resolved": "https://registry.npmjs.org/next-line/-/next-line-1.1.0.tgz", 516 | "integrity": "sha512-+I10J3wKNoKddNxn0CNpoZ3eTZuqxjNM3b1GImVx22+ePI+Y15P8g/j3WsbP0fhzzrFzrtjOAoq5NCCucswXOQ==" 517 | }, 518 | "normalize-package-data": { 519 | "version": "2.5.0", 520 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 521 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 522 | "dependencies": { 523 | "semver": { 524 | "version": "5.7.1", 525 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 526 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 527 | } 528 | } 529 | }, 530 | "object-filter-sequence": { 531 | "version": "1.0.0", 532 | "resolved": "https://registry.npmjs.org/object-filter-sequence/-/object-filter-sequence-1.0.0.tgz", 533 | "integrity": "sha512-CsubGNxhIEChNY4cXYuA6KXafztzHqzLLZ/y3Kasf3A+sa3lL9thq3z+7o0pZqzEinjXT6lXDPAfVWI59dUyzQ==" 534 | }, 535 | "object-identity-map": { 536 | "version": "1.0.2", 537 | "resolved": "https://registry.npmjs.org/object-identity-map/-/object-identity-map-1.0.2.tgz", 538 | "integrity": "sha512-a2XZDGyYTngvGS67kWnqVdpoaJWsY7C1GhPJvejWAFCsUioTAaiTu8oBad7c6cI4McZxr4CmvnZeycK05iav5A==" 539 | }, 540 | "object-inspect": { 541 | "version": "1.12.3", 542 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 543 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" 544 | }, 545 | "object-keys": { 546 | "version": "1.1.1", 547 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 548 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" 549 | }, 550 | "object.assign": { 551 | "version": "4.1.4", 552 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", 553 | "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==" 554 | }, 555 | "object.entries": { 556 | "version": "1.1.6", 557 | "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", 558 | "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==" 559 | }, 560 | "once": { 561 | "version": "1.4.0", 562 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 563 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==" 564 | }, 565 | "optional-js": { 566 | "version": "2.3.0", 567 | "resolved": "https://registry.npmjs.org/optional-js/-/optional-js-2.3.0.tgz", 568 | "integrity": "sha512-B0LLi+Vg+eko++0z/b8zIv57kp7HKEzaPJo7LowJXMUKYdf+3XJGu/cw03h/JhIOsLnP+cG5QnTHAuicjA5fMw==" 569 | }, 570 | "original-url": { 571 | "version": "1.2.3", 572 | "resolved": "https://registry.npmjs.org/original-url/-/original-url-1.2.3.tgz", 573 | "integrity": "sha512-BYm+pKYLtS4mVe/mgT3YKGtWV5HzN/XKiaIu1aK4rsxyjuHeTW9N+xVBEpJcY1onB3nccfH0RbzUEoimMqFUHQ==" 574 | }, 575 | "p-limit": { 576 | "version": "2.3.0", 577 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 578 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==" 579 | }, 580 | "p-locate": { 581 | "version": "4.1.0", 582 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 583 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==" 584 | }, 585 | "p-try": { 586 | "version": "2.2.0", 587 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 588 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 589 | }, 590 | "parse-json": { 591 | "version": "5.2.0", 592 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 593 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==" 594 | }, 595 | "path-exists": { 596 | "version": "4.0.0", 597 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 598 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 599 | }, 600 | "path-parse": { 601 | "version": "1.0.7", 602 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 603 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 604 | }, 605 | "pino": { 606 | "version": "6.14.0", 607 | "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", 608 | "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==" 609 | }, 610 | "pino-std-serializers": { 611 | "version": "3.2.0", 612 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", 613 | "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" 614 | }, 615 | "process-warning": { 616 | "version": "1.0.0", 617 | "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", 618 | "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" 619 | }, 620 | "pseudomap": { 621 | "version": "1.0.2", 622 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 623 | "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" 624 | }, 625 | "punycode": { 626 | "version": "2.3.0", 627 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 628 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" 629 | }, 630 | "quick-format-unescaped": { 631 | "version": "4.0.4", 632 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", 633 | "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" 634 | }, 635 | "random-poly-fill": { 636 | "version": "1.0.1", 637 | "resolved": "https://registry.npmjs.org/random-poly-fill/-/random-poly-fill-1.0.1.tgz", 638 | "integrity": "sha512-bMOL0hLfrNs52+EHtIPIXxn2PxYwXb0qjnKruTjXiM/sKfYqj506aB2plFwWW1HN+ri724bAVVGparh4AtlJKw==" 639 | }, 640 | "read-pkg": { 641 | "version": "5.2.0", 642 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", 643 | "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", 644 | "dependencies": { 645 | "type-fest": { 646 | "version": "0.6.0", 647 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", 648 | "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" 649 | } 650 | } 651 | }, 652 | "read-pkg-up": { 653 | "version": "7.0.1", 654 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", 655 | "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==" 656 | }, 657 | "readable-stream": { 658 | "version": "3.6.0", 659 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 660 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==" 661 | }, 662 | "regexp.prototype.flags": { 663 | "version": "1.4.3", 664 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", 665 | "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==" 666 | }, 667 | "relative-microtime": { 668 | "version": "2.0.0", 669 | "resolved": "https://registry.npmjs.org/relative-microtime/-/relative-microtime-2.0.0.tgz", 670 | "integrity": "sha512-l18ha6HEZc+No/uK4GyAnNxgKW7nvEe35IaeN54sShMojtqik2a6GbTyuiezkjpPaqP874Z3lW5ysBo5irz4NA==" 671 | }, 672 | "require-in-the-middle": { 673 | "version": "5.2.0", 674 | "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", 675 | "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==" 676 | }, 677 | "resolve": { 678 | "version": "1.22.1", 679 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 680 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==" 681 | }, 682 | "rfdc": { 683 | "version": "1.3.0", 684 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", 685 | "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" 686 | }, 687 | "safe-buffer": { 688 | "version": "5.1.2", 689 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 690 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 691 | }, 692 | "safe-regex-test": { 693 | "version": "1.0.0", 694 | "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", 695 | "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==" 696 | }, 697 | "semver": { 698 | "version": "6.3.0", 699 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 700 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 701 | }, 702 | "set-cookie-serde": { 703 | "version": "1.0.0", 704 | "resolved": "https://registry.npmjs.org/set-cookie-serde/-/set-cookie-serde-1.0.0.tgz", 705 | "integrity": "sha512-Vq8e5GsupfJ7okHIvEPcfs5neCo7MZ1ZuWrO3sllYi3DOWt6bSSCpADzqXjz3k0fXehnoFIrmmhty9IN6U6BXQ==" 706 | }, 707 | "shallow-clone-shim": { 708 | "version": "2.0.0", 709 | "resolved": "https://registry.npmjs.org/shallow-clone-shim/-/shallow-clone-shim-2.0.0.tgz", 710 | "integrity": "sha512-YRNymdiL3KGOoS67d73TEmk4tdPTO9GSMCoiphQsTcC9EtC+AOmMPjkyBkRoCJfW9ASsaZw1craaiw1dPN2D3Q==" 711 | }, 712 | "shimmer": { 713 | "version": "1.2.1", 714 | "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", 715 | "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" 716 | }, 717 | "side-channel": { 718 | "version": "1.0.4", 719 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 720 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==" 721 | }, 722 | "sonic-boom": { 723 | "version": "1.4.1", 724 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", 725 | "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==" 726 | }, 727 | "source-map": { 728 | "version": "0.5.7", 729 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 730 | "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" 731 | }, 732 | "spdx-correct": { 733 | "version": "3.1.1", 734 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", 735 | "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==" 736 | }, 737 | "spdx-exceptions": { 738 | "version": "2.3.0", 739 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", 740 | "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" 741 | }, 742 | "spdx-expression-parse": { 743 | "version": "3.0.1", 744 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 745 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==" 746 | }, 747 | "spdx-license-ids": { 748 | "version": "3.0.12", 749 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", 750 | "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==" 751 | }, 752 | "sql-summary": { 753 | "version": "1.0.1", 754 | "resolved": "https://registry.npmjs.org/sql-summary/-/sql-summary-1.0.1.tgz", 755 | "integrity": "sha512-IpCr2tpnNkP3Jera4ncexsZUp0enJBLr+pHCyTweMUBrbJsTgQeLWx1FXLhoBj/MvcnUQpkgOn2EY8FKOkUzww==" 756 | }, 757 | "stackframe": { 758 | "version": "1.3.4", 759 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", 760 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" 761 | }, 762 | "stackman": { 763 | "version": "4.0.1", 764 | "resolved": "https://registry.npmjs.org/stackman/-/stackman-4.0.1.tgz", 765 | "integrity": "sha512-lntIge3BFEElgvpZT2ld5f4U+mF84fRtJ8vA3ymUVx1euVx43ZMkd09+5RWW4FmvYDFhZwPh1gvtdsdnJyF4Fg==" 766 | }, 767 | "stream-chopper": { 768 | "version": "3.0.1", 769 | "resolved": "https://registry.npmjs.org/stream-chopper/-/stream-chopper-3.0.1.tgz", 770 | "integrity": "sha512-f7h+ly8baAE26iIjcp3VbnBkbIRGtrvV0X0xxFM/d7fwLTYnLzDPTXRKNxa2HZzohOrc96NTrR+FaV3mzOelNA==" 771 | }, 772 | "string-similarity": { 773 | "version": "4.0.4", 774 | "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", 775 | "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" 776 | }, 777 | "string.prototype.trimend": { 778 | "version": "1.0.6", 779 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", 780 | "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==" 781 | }, 782 | "string.prototype.trimstart": { 783 | "version": "1.0.6", 784 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", 785 | "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==" 786 | }, 787 | "string_decoder": { 788 | "version": "1.3.0", 789 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 790 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 791 | "dependencies": { 792 | "safe-buffer": { 793 | "version": "5.2.1", 794 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 795 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 796 | } 797 | } 798 | }, 799 | "supports-color": { 800 | "version": "5.5.0", 801 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 802 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" 803 | }, 804 | "supports-preserve-symlinks-flag": { 805 | "version": "1.0.0", 806 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 807 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" 808 | }, 809 | "to-source-code": { 810 | "version": "1.0.2", 811 | "resolved": "https://registry.npmjs.org/to-source-code/-/to-source-code-1.0.2.tgz", 812 | "integrity": "sha512-YzWtjmNIf3E75eZYa7m1SCyl0vgOGoTzdpH3svfa8SUm5rqTgl9hnDolrAGOghCF9P2gsITXQoMrlujOoz+RPw==" 813 | }, 814 | "traceparent": { 815 | "version": "1.0.0", 816 | "resolved": "https://registry.npmjs.org/traceparent/-/traceparent-1.0.0.tgz", 817 | "integrity": "sha512-b/hAbgx57pANQ6cg2eBguY3oxD6FGVLI1CC2qoi01RmHR7AYpQHPXTig9FkzbWohEsVuHENZHP09aXuw3/LM+w==" 818 | }, 819 | "traverse": { 820 | "version": "0.6.7", 821 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", 822 | "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==" 823 | }, 824 | "type-fest": { 825 | "version": "0.8.1", 826 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", 827 | "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" 828 | }, 829 | "typed-array-length": { 830 | "version": "1.0.4", 831 | "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", 832 | "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==" 833 | }, 834 | "unbox-primitive": { 835 | "version": "1.0.2", 836 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", 837 | "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==" 838 | }, 839 | "unicode-byte-truncate": { 840 | "version": "1.0.0", 841 | "resolved": "https://registry.npmjs.org/unicode-byte-truncate/-/unicode-byte-truncate-1.0.0.tgz", 842 | "integrity": "sha512-GQgHk6DodEoKddKQdjnv7xKS9G09XCfHWX0R4RKht+EbUMSiVEmtWHGFO8HUm+6NvWik3E2/DG4MxTitOLL64A==" 843 | }, 844 | "unicode-substring": { 845 | "version": "0.1.0", 846 | "resolved": "https://registry.npmjs.org/unicode-substring/-/unicode-substring-0.1.0.tgz", 847 | "integrity": "sha512-36Xaw9wXi7MB/3/EQZZHkZyyiRNa9i3k9YtPAz2KfqMVH2xutdXyMHn4Igarmnvr+wOrfWa/6njhY+jPpXN2EQ==" 848 | }, 849 | "uri-js": { 850 | "version": "4.4.1", 851 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 852 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==" 853 | }, 854 | "util-deprecate": { 855 | "version": "1.0.2", 856 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 857 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 858 | }, 859 | "validate-npm-package-license": { 860 | "version": "3.0.4", 861 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 862 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==" 863 | }, 864 | "which-boxed-primitive": { 865 | "version": "1.0.2", 866 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 867 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==" 868 | }, 869 | "which-typed-array": { 870 | "version": "1.1.9", 871 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", 872 | "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==" 873 | }, 874 | "wrappy": { 875 | "version": "1.0.2", 876 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 877 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 878 | }, 879 | "yallist": { 880 | "version": "2.1.2", 881 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 882 | "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" 883 | } 884 | } 885 | } 886 | --------------------------------------------------------------------------------