From 2bc33f678530dc6f943b7e46d758d3a29fb25476 Mon Sep 17 00:00:00 2001 From: code-xhyun Date: Wed, 10 Apr 2024 00:26:41 +0900 Subject: [PATCH] init --- .editorconfig | 7 + .eslintrc.js | 19 + .github/workflows/codeql-analysis.yml | 72 + .github/workflows/docs.yml | 32 + .github/workflows/test.yml | 53 + .gitignore | 38 +- .mocharc.jsonc | 13 + .npmignore | 12 + .nycrc | 19 + .prettierrc | 7 + CHANGELOG.md | 4 + History.md | 625 ++ README.md | 1285 +++- agendats.png | Bin 0 -> 6119 bytes docs/_config.yml | 1 + docs/agenda/6.x/.nojekyll | 1 + docs/agenda/6.x/assets/highlight.css | 113 + docs/agenda/6.x/assets/icons.css | 1043 +++ docs/agenda/6.x/assets/icons.png | Bin 0 -> 9615 bytes docs/agenda/6.x/assets/icons@2x.png | Bin 0 -> 28144 bytes docs/agenda/6.x/assets/main.js | 52 + docs/agenda/6.x/assets/search.js | 1 + docs/agenda/6.x/assets/style.css | 1413 ++++ docs/agenda/6.x/assets/widgets.png | Bin 0 -> 480 bytes docs/agenda/6.x/assets/widgets@2x.png | Bin 0 -> 855 bytes docs/agenda/6.x/classes/Agenda.html | 269 + docs/agenda/6.x/classes/Job.html | 53 + docs/agenda/6.x/index.html | 819 +++ docs/agenda/6.x/interfaces/IAgendaConfig.html | 1 + .../6.x/interfaces/IDatabaseOptions.html | 1 + docs/agenda/6.x/interfaces/IDbConfig.html | 1 + .../agenda/6.x/interfaces/IJobDefinition.html | 9 + .../agenda/6.x/interfaces/IJobParameters.html | 4 + docs/agenda/6.x/interfaces/IMongoOptions.html | 1 + docs/agenda/6.x/modules.html | 1 + docs/index.md | 17 + es.js | 1 + examples/concurrency.js | 71 + package-lock.json | 6148 +++++++++++++++++ package.json | 85 + src/Job.ts | 496 ++ src/JobDbRepository.ts | 400 ++ src/JobProcessingQueue.ts | 128 + src/JobProcessor.ts | 642 ++ src/index.ts | 579 ++ src/types/AgendaConfig.ts | 15 + src/types/AgendaStatus.ts | 30 + src/types/DbOptions.ts | 23 + src/types/JobDefinition.ts | 20 + src/types/JobParameters.ts | 51 + src/utils/hasMongoProtocol.ts | 1 + src/utils/isValidDate.ts | 4 + src/utils/nextRunAt.ts | 107 + src/utils/priority.ts | 24 + src/utils/processEvery.ts | 6 + src/utils/stack.ts | 21 + test/agenda.test.ts | 782 +++ test/fixtures/add-tests.ts | 76 + test/fixtures/agenda-instance.ts | 35 + test/fixtures/someJobDefinition.ts | 12 + test/helpers/forkHelper.ts | 63 + test/helpers/mock-mongodb.ts | 28 + test/job.test.ts | 1789 +++++ test/jobprocessor.test.ts | 280 + test/retry.test.ts | 86 + test/tsconfig.json | 9 + tsconfig.eslint.json | 14 + tsconfig.json | 28 + 68 files changed, 18022 insertions(+), 18 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.js create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/test.yml create mode 100644 .mocharc.jsonc create mode 100644 .npmignore create mode 100644 .nycrc create mode 100644 .prettierrc create mode 100644 CHANGELOG.md create mode 100644 History.md create mode 100644 agendats.png create mode 100644 docs/_config.yml create mode 100644 docs/agenda/6.x/.nojekyll create mode 100644 docs/agenda/6.x/assets/highlight.css create mode 100644 docs/agenda/6.x/assets/icons.css create mode 100644 docs/agenda/6.x/assets/icons.png create mode 100644 docs/agenda/6.x/assets/icons@2x.png create mode 100644 docs/agenda/6.x/assets/main.js create mode 100644 docs/agenda/6.x/assets/search.js create mode 100644 docs/agenda/6.x/assets/style.css create mode 100644 docs/agenda/6.x/assets/widgets.png create mode 100644 docs/agenda/6.x/assets/widgets@2x.png create mode 100644 docs/agenda/6.x/classes/Agenda.html create mode 100644 docs/agenda/6.x/classes/Job.html create mode 100644 docs/agenda/6.x/index.html create mode 100644 docs/agenda/6.x/interfaces/IAgendaConfig.html create mode 100644 docs/agenda/6.x/interfaces/IDatabaseOptions.html create mode 100644 docs/agenda/6.x/interfaces/IDbConfig.html create mode 100644 docs/agenda/6.x/interfaces/IJobDefinition.html create mode 100644 docs/agenda/6.x/interfaces/IJobParameters.html create mode 100644 docs/agenda/6.x/interfaces/IMongoOptions.html create mode 100644 docs/agenda/6.x/modules.html create mode 100644 docs/index.md create mode 100644 es.js create mode 100644 examples/concurrency.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/Job.ts create mode 100644 src/JobDbRepository.ts create mode 100644 src/JobProcessingQueue.ts create mode 100644 src/JobProcessor.ts create mode 100644 src/index.ts create mode 100644 src/types/AgendaConfig.ts create mode 100644 src/types/AgendaStatus.ts create mode 100644 src/types/DbOptions.ts create mode 100644 src/types/JobDefinition.ts create mode 100644 src/types/JobParameters.ts create mode 100644 src/utils/hasMongoProtocol.ts create mode 100644 src/utils/isValidDate.ts create mode 100644 src/utils/nextRunAt.ts create mode 100644 src/utils/priority.ts create mode 100644 src/utils/processEvery.ts create mode 100644 src/utils/stack.ts create mode 100644 test/agenda.test.ts create mode 100644 test/fixtures/add-tests.ts create mode 100644 test/fixtures/agenda-instance.ts create mode 100644 test/fixtures/someJobDefinition.ts create mode 100644 test/helpers/forkHelper.ts create mode 100644 test/helpers/mock-mongodb.ts create mode 100644 test/job.test.ts create mode 100644 test/jobprocessor.test.ts create mode 100644 test/retry.test.ts create mode 100644 test/tsconfig.json create mode 100644 tsconfig.eslint.json create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9c73aba --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +charset=utf-8 +end_of_line=lf +trim_trailing_whitespace=true +insert_final_newline=true +indent_style=space +indent_size=2 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..fa3f704 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,19 @@ +module.exports = { + root: true, + extends: ['@hokify'], + parserOptions: { + project: './tsconfig.eslint.json' + }, + overrides: [ + { + files: ['*.test.ts'], + env: { + mocha: true + }, + rules: { + '@typescript-eslint/no-unused-expressions': 'off', + 'import/no-relative-packages': 'off' + } + } + ] +}; diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..1b1d16f --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '25 1 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..ea21e9d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,32 @@ +name: Run Test +on: [push, workflow_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Git checkout + uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v2 + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-agenda-${{ hashFiles('**/package-lock.lock') }} + restore-keys: | + ${{ runner.os }}-build-agenda- + + - name: Update npm + run: npm -g install npm@latest + + - name: Install Packages + run: npm install + + - name: Docs + run: npm run docs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..fe67fbd --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,53 @@ +name: Run Test +on: [push, pull_request, workflow_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14, 16, 18] + mongodb-version: [3.6, 4.4, 5.0, 6.0] + steps: + - name: Git checkout + uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.6.0 + with: + mongodb-version: ${{ matrix.mongodb-version }} + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-agenda-${{ hashFiles('**/package-lock.lock') }} + restore-keys: | + ${{ runner.os }}-build-agenda- + + - name: Update npm + run: npm -g install npm@latest + + - name: Install Packages + run: npm install + + - name: Run Lint + run: npm run lint + + - name: Build + run: npm run build + env: + CI: true + + - name: Test + run: npm run test + env: + CI: true diff --git a/.gitignore b/.gitignore index c6bba59..dc79339 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,18 @@ +node_modules +coverage.html +.idea +.DS_Store +docs/agenda/* +!docs/agenda/1.0.3/ +!docs/agenda/2.0.0/ +!docs/agenda/2.2.0/ +!docs/agenda/3.1.0/ +!docs/agenda/4.0.1/ +!docs/agenda/4.2.0/ +dist +.ultra.cache.json +.vscode + # Logs logs *.log @@ -5,7 +20,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* -.pnpm-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json @@ -54,9 +68,6 @@ web_modules/ # Optional eslint cache .eslintcache -# Optional stylelint cache -.stylelintcache - # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ @@ -72,12 +83,9 @@ web_modules/ # Yarn Integrity file .yarn-integrity -# dotenv environment variable files +# dotenv environment variables file .env -.env.development.local -.env.test.local -.env.production.local -.env.local +.env.test # parcel-bundler cache (https://parceljs.org/) .cache @@ -100,13 +108,6 @@ dist # vuepress build output .vuepress/dist -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - # Serverless directories .serverless/ @@ -128,3 +129,8 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +!docs/agenda/4.x/ +!docs/agenda/6.x/ +dist +.nyc_output +coverage diff --git a/.mocharc.jsonc b/.mocharc.jsonc new file mode 100644 index 0000000..2eac2aa --- /dev/null +++ b/.mocharc.jsonc @@ -0,0 +1,13 @@ +{ + "diff": true, + "spec": "./test/*.test.ts", + "require": ["ts-node/register", "source-map-support/register"], + "extension": ["js", "ts"], + "package": "./package.json", + "recursive": true, + "reporter": "spec", + "slow": 75, + "timeout": 25000, + "ui": "bdd", + "exit": true +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..100e236 --- /dev/null +++ b/.npmignore @@ -0,0 +1,12 @@ +.editorconfig +.idea +.jsdoc.json +.travis.yml +agenda.svg +docs +.nyc_output +coverage +History.md +Makefile +renovate.json +test diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000..0dfbc82 --- /dev/null +++ b/.nycrc @@ -0,0 +1,19 @@ +{ + "extends": "@istanbuljs/nyc-config-typescript", + "all": false, + "include": [ + "src" + ], + "exclude": [ + ".eslintrc.js", + "index.js", + "docs", + "coverage", + "test" + ], + "reporter": [ + "text", + "lcov", + "text-summary" + ] +} \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9cd30a9 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "singleQuote": true, + "useTabs": true, + "trailingComma": "none", + "arrowParens": "avoid" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ed7dd52 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +0.1.0 / 2024-04-09 +================== + + * Initial diff --git a/History.md b/History.md new file mode 100644 index 0000000..734b632 --- /dev/null +++ b/History.md @@ -0,0 +1,625 @@ +# Next + +_Contributions welcome!_ https://github.com/agenda/agenda/pulls + + # [5.0.0](https://github.com/agenda/agenda/releases/tag/v5.0.0) / 2022-11-07 + +## BREAKING + +- MongoDB 3.4 and 3.6 are not supported anymore. Only MongoDB v4.0 and above are supported. + +Otherwise, all the changes are minor: https://github.com/agenda/agenda/releases/tag/v5.0.0 + + # [4.4.0](https://github.com/agenda/agenda/releases/tag/v4.4.0) / 2022-10-19 + +- Feat: Add `drain()` method for graceful process shutdowns ([#1488](https://github.com/agenda/agenda/pull/1488)) thanks @nmehmedrnd + + # [4.3.0](https://github.com/agenda/agenda/releases/tag/v4.3.0) / 2022-05-10 + +- Feat: `disableAutoIndex` +- Feat: `shouldSaveResult` + + # 4.2.1 / 2021-08-09 + +- fix: deprecation warning for collection.findOneAndUpdate ([#1338](https://github.com/agenda/agenda/pull/1338)) thanks @frobinsonj + + # 4.2.0 / 2021-08-05 + +- Feat: Add top level disable and enable ([#1109](https://github.com/agenda/agenda/pull/1109)) thanks @pdfowler +- fix(history): match cron-parser pattern ([#1335](https://github.com/agenda/agenda/pull/1335)) thanks @dockleryxk +- fix: "RangeError: Maximum call stack size exceeded" ([#1365](https://github.com/agenda/agenda/pull/1365)) thanks @koresar +- fix: JobAttributes.lockedAt to allow null values ([#1340](https://github.com/agenda/agenda/pull/1340)) thanks @tjramage +- Updated dependencies: `mongodb@3.6.6`, `lodash@4.17.21`, as well as various dev dependencies. +- Added node 16 in tests ([#1314](https://github.com/agenda/agenda/pull/1086)) thanks @simison + + # 4.1.3 / 2021-05-02 + +- fix: export cjs and es (#1298) thanks @koresar + + # 4.1.2 / 2021-05-02 + +- fix: broken main cjs export works again. See more [here](https://github.com/agenda/agenda/issues/1266#issuecomment-830628762). + + # 4.1.2 / 2021-04-04 + +- Docs, JSDocs and TS typo fixes. Thanks @niftylettuce @thebestnom @simllll and @Igor-lkm + +- fix: typescript export needs es6 ([#1268](https://github.com/agenda/agenda/pull/#1268)) thanks @simllll + + # 4.1.1 / 2021-03-02 + +- Compatibility with DefinitelyTyped/agenda ([#1258](https://github.com/agenda/agenda/pull/1258)) thanks @boredland + + # 4.1.0 / 2021-02-25 + +- Added type information ([#1202](https://github.com/agenda/agenda/pull/1202) [#1243](https://github.com/agenda/agenda/pull/1243)) thanks @boredland, @leonardlin + + # 4.0.1 / 2021-01-16 + +- Fix _"Cannot find module ./lib/agenda"_ bug due us not targeting correct ES5 files for distribution after the TypeScript rewrite. ([#1193](https://github.com/agenda/agenda/pull/1193)) +- Update dependencies + + # 4.0.0 / 2021-01-16 + +- Add `agenda.close()` ([#450](https://github.com/agenda/agenda/pull/450)) thanks @simison + +- Add ability to schedule jobs with startDate, endDate, and skipping ([#361](https://github.com/agenda/agenda/pull/361)) thanks @suryanaik + +- Fix issue with concurrent locking beyond locklimit ([#1086](https://github.com/agenda/agenda/pull/1086)) thanks @leonardlin + +- Fix issue with too many locks being set asynchronously ([#1119](https://github.com/agenda/agenda/pull/1119)) thanks @GimpMaster + +- Upgrade `mongodb` dependency ~3.5.0 -> ~3.6.2 (security) ([#1122](https://github.com/agenda/agenda/pull/1122)) thanks @Elisa23 + +- Upgrade to [Human Interval v2](https://github.com/agenda/human-interval/blob/master/History.md#200--2020-10-16), a refactor using [numbered](https://www.npmjs.com/package/numbered) package: + + - Supports all the formats as previously, and more! + - Supports numbers written as English words (one, two hundred) + - Supports time expressions in singular and plural (minute and minutes) + - Supports negative numbers (-2) + - Supports hyphenated words (twenty-five) + +- Upgrade various dependencies + +## BREAKING + +- Switch from [ncb000gt/node-cron](https://www.npmjs.com/package/cron) to [harrisiirak/cron-parser](https://www.npmjs.com/package/cron-parser) for cron-pattern parsing. See issue ([#475](https://github.com/kelektiv/node-cron/issues/475)) + + Previously month was 0-based (0=January). Going forward standard Unix pattern is used, which is 1-based (1=January). + + Please update existing cron-patterns that specify a month (4th position of a pattern). The month is now 1 - 12 + + 1 = January + + 2 = February + + 3... + + | Example | Execute on 1st of January | + |---------|---------------------------| + | Old | 0 0 1 **0** * | + | New | 0 0 1 **1** * | + + ([#1150](https://github.com/agenda/agenda/pull/1150)) + + old Cron patterns + + ``` + * * * * * * + | | | | | | + | | | | | +-- Year (range: 1900-3000) + | | | | +---- Day of the Week (range: 1-7, 1 standing for Monday) + | | | +------ Month of the Year (range: 0-11) NOTE: Difference here + | | +-------- Day of the Month (range: 1-31) + | +---------- Hour (range: 0-23) + +------------ Minute (range: 0-59) + ``` + + new cron patterns + + ``` + * * * * * * + | | | | | | + | | | | | +-- Day of the Week (range: 0-7, 0 or 7 is Sunday) + | | | | +---- Month of the Year (range: 1-12) NOTE: Difference here + | | | +------ Day of the Month (range: 1-31) + | | +-------- Hour (range: 0-23) + | +---------- Minute (range: 0-59) + +------------ Second (range: 0-59, optional) + ``` + + # 3.1.0 / 2020-04-07 + +_Stay safe!_ + +- Fix for skipImmediate resetting nextRunAt to current date ([#860](https://github.com/agenda/agenda/pull/860)) (Thanks @AshlinDuncan!) +- Fix deprecated reconnect options ([#948](https://github.com/agenda/agenda/pull/948)) (Thanks @ekegodigital!) +- Add ability to set a skip when querying jobs. ([#898](https://github.com/agenda/agenda/pull/898)) (Thanks @cjolif!) + +Internal: + +- Fixed deprecated MongoDB functions in tests ([#928](https://github.com/agenda/agenda/pull/928)) (Thanks @MichielDeMey!) +- Updated devDependencies + +Thank you @koresar, @sampathBlam, and @MichielDeMey helping to review PRs for this release! 👏 + +# 3.0.0 / 2020-02-13 + +- Support MongoDB's Unified Topology Design ([#921](https://github.com/agenda/agenda/pull/921)) (Thanks @viktorzavadil!) +- Fix: check that the new nextRunAt is different that the previous nextRunAt ([#863](https://github.com/agenda/agenda/pull/863)) (Thanks @RaphaelRheault!) +- Update dependencies. Most notably MongoDB driver 3.4 → 3.5 ([#899](https://github.com/agenda/agenda/pull/899), [#900](https://github.com/agenda/agenda/pull/900), [#903](https://github.com/agenda/agenda/pull/903), [#906](https://github.com/agenda/agenda/pull/906), [#908](https://github.com/agenda/agenda/pull/908), [#910](https://github.com/agenda/agenda/pull/910), [#912](https://github.com/agenda/agenda/pull/912), [#913](https://github.com/agenda/agenda/pull/913), [#920](https://github.com/agenda/agenda/pull/920), [#922](https://github.com/agenda/agenda/pull/922)) +- Documentation updates, thanks @MichielDeMey and @Sunghee2. ([#923](https://github.com/agenda/agenda/pull/923) & [#907](https://github.com/agenda/agenda/pull/907)) + +## BREAKING + +- Stop testing for Node.js 8. This might still work but we're no longer actively testing for it. ([#925](https://github.com/agenda/agenda/pull/925)) + + # 2.3.0 / 2019-12-16 + +- Improved performance in situations when there are many "expired" jobs in the database ([#869](https://github.com/agenda/agenda/pull/869)) (Thanks @mfred488!) +- Fix periodic node.js process unhandledRejection ([#887](https://github.com/agenda/agenda/pull/887)) (Thanks @koresar and @Scorpil) +- Update dependencies + + # 2.2.0 / 2019-11-24 + + - Fix `skipImmediate` option in `.every` ([#861](https://github.com/agenda/agenda/pull/861)) (Thanks @erics2783!) + - Add try/catch block to agenda#now method ([#876](https://github.com/agenda/agenda/pull/876)) (Thanks @sampathBlam!) + - Refactor job queuing mechanism. Agenda n ow guarantees priority when executing jobs scheduled the same datetime. Fixes also some tests. ([#852](https://github.com/agenda/agenda/pull/852)) (Thank you @dmbarreiro!) + - Update dependencies (Kudos @simison!) + Most notably `mongodb` ~3.2.7 -> ~3.3.0 ([changelog](https://github.com/mongodb/node-mongodb-native/tree/v3.3.0)) — highlights: + - Mongo DB Server Version 4.2 feature support + - Merged `mongodb-core` into `node-mongodb-native` + - Beta support for MongoDB Client-Side Encryption + - SRV Polling for Sharded Clusters + - Updates to documentation (Thank you @lautarobock, @sampathBlam, @indatawetrust) + + # 2.1.0 / 2019-09-09 + + - Support async functions in job processing ([#653](https://github.com/agenda/agenda/pull/653)) (thanks @princjef!) + - Allow sorting and limiting jobs when searching ([#665](https://github.com/agenda/agenda/pull/665)) (thank you @edwin-jones) + - Update MongoClient connection settings with `useNewUrlParser: true` to remove the deprecation warning. ([#806](https://github.com/agenda/agenda/pull/806)) (thanks @dpawson905!) + - Allow valid date strings when scheduling ([#808](https://github.com/agenda/agenda/pull/808)) (Thanks @wingsbob!) + - Update dependencies ([#820](https://github.com/agenda/agenda/pull/820)) + - Update documentation (kudos @dandv, @pedruino and many others!) + - Fix linting errors ([#847](https://github.com/agenda/agenda/pull/847)) (thanks @dmbarreiro!) + + # 2.0.2 / 2018-09-15 + + - Fixes a MongoDB connection string issue with Atlas ([#674](https://github.com/agenda/agenda/pull/674) + + # 2.0.1 / 2018-08-30 + + - Fix a bug where `job.touch()` wasn't promise based, as it should've been ([#667](https://github.com/agenda/agenda/pull/667) + + # 2.0.0 / 2018-07-19 + + - Rewrite tests: replace `mocha` and `blanket` with `ava` and `nyc` ([#506](https://github.com/agenda/agenda/pull/506)) + - Optimization: don't try and unlock jobs when `_lockedJobs` is empty ([#509](https://github.com/agenda/agenda/pull/509)) + - Code cleanup ([#503](https://github.com/agenda/agenda/pull/503)) + - Ensure tests pass for Node.js version 10 [#608](https://github.com/agenda/agenda/pull/608)) + - Add `skipImmediate` to `repeatEvery()` options to skip immediate run of repeated jobs when Agenda starts. See [documentation](https://github.com/agenda/agenda/blob/202c9e95b40115dc763641f55180db9a4f358272/README.md#repeateveryinterval-options) ([#594](https://github.com/agenda/agenda/pull/594)) + - Fixes some flaky tests + - Adds docs generator (`npm run docs` to generate `/docs`) + +## BREAKING + +- Rewrite Agenda API support promises! ([#557](https://github.com/agenda/agenda/pull/557)) + + No more callbacks! Instead of: + + ```js + function graceful() { + agenda.stop(function () { + process.exit(0); + }); + } + ``` + + You need to: + + ```js + async function graceful() { + await agenda.stop(); + process.exit(0); + } + ``` + + You don't anymore have to listen for `start` event. Instead you can do: + + ```js + await agenda.start(); + agenda.every("10 minutes", "example"); + ``` + + However, this will still work: + + ```js + agenda.on("ready", function () { + agenda.every("10 minutes", "example"); + agenda.start(); + }); + ``` + + See the documentation for more! + +- Drop support for Node.js versions 4, 5 and 6 ([#557](https://github.com/agenda/agenda/pull/557) / [#608](https://github.com/agenda/agenda/pull/608)) +- Drop support for MongoDB 2.4 ([#497](https://github.com/agenda/agenda/pull/497)) +- Update Native MongoDB driver to 3.1 from 2.2 ([#616](https://github.com/agenda/agenda/pull/616)) +- Jobs _emit_ errors instead of throwing them + +# 1.0.3 / 2017-10-17 + +- Update dependencies ([2854c7e](https://github.com/agenda/agenda/commit/65159172b34b9a1344814619c117474bcc323f8d)) + +# 1.0.2 / 2017-10-17 + +- Update dependencies ([2854c7e](https://github.com/agenda/agenda/commit/2854c7e3847cc8aecea702df8532789c51b1ed30)) + +# 1.0.1 / 2017-10-10 + +- Update dependencies `cron` and `debug` ([#505](https://github.com/agenda/agenda/pull/505)) + +# 1.0.0 / 2017-08-12 + +- Gracefully recover from losing connection to MongoDB ([#472](https://github.com/agenda/agenda/pull/472)) +- Code cleanup ([#492](https://github.com/agenda/agenda/pull/492)) + +## BREAKING + +- Fix jobs not running in order of them being queued ([#464](https://github.com/agenda/agenda/pull/464)) + +- Changes in Cron string parsing, changed parsing library from [ncb000gt/node-cron](https://www.npmjs.com/package/cron) to [harrisiirak/cron-parser](https://www.npmjs.com/package/cron-parser) ([#475](https://github.com/agenda/agenda/pull/475)) + +Previously Agenda would treat months as 0-11 where as normally, cron months are parsed as 1-12. + +``` +* * * * * * +| | | | | | +| | | | | +-- Year (range: 1900-3000) +| | | | +---- Day of the Week (range: 1-7, 1 standing for Monday) +| | | +------ Month of the Year (range: 0-11) NOTE: Difference here +| | +-------- Day of the Month (range: 1-31) +| +---------- Hour (range: 0-23) ++------------ Minute (range: 0-59) +``` + +Starting in version `1.0.0`, cron will be parsed in the standard UNIX style: + +``` +* * * * * * +| | | | | | +| | | | | +-- Year (range: 1900-3000) +| | | | +---- Day of the Week (range: 1-7, 1 standing for Monday) +| | | +------ Month of the Year (range: 1-12) NOTE: Difference here +| | +-------- Day of the Month (range: 1-31) +| +---------- Hour (range: 0-23) ++------------ Minute (range: 0-59) +``` + +# 0.10.2 / 2017-08-10 + +- Adds debugging, [see instructions from README.md](https://github.com/agenda/agenda#to-turn-on-logging-please-set-your-debug-env-variable-like-so). + +# 0.10.1 / 2017-08-10 + +- Unpublished and re-published as v0.10.2 + +# 0.10.0 / 2017-08-08 + +- Replace the deprecated `findAndModify` method from native MongoDB driver to `findOneAndUpdate` ([#448](https://github.com/agenda/agenda/pull/448)) +- Going forward, we won't ensure Node.js v0.10 and v0.11 compatibility anymore ([#449](https://github.com/agenda/agenda/pull/449)) +- Code cleanup ([#491](https://github.com/agenda/agenda/pull/491), [#489](https://github.com/agenda/agenda/pull/489), [#488](https://github.com/agenda/agenda/pull/488), [#487](https://github.com/agenda/agenda/pull/487)) + +# 0.9.1 / 2017-03-22 + +Republish release for NPM. Includes fixes from 0.9.0 release: + +- add support for `mongoose.connection` for `agenda.mongo()`, fixes #156 +- Fix for race condition in the afterEach clean up code (#355) +- Fixes + protects against concurrency not being honored (#379) + +# 0.9.0 / 2016-12-28 + +- add support for `mongoose.connection` for `agenda.mongo()`, fixes #156 +- Fix for race condition in the afterEach clean up code (#355) +- Fixes + protects against concurrency not being honored (#379) +- Bump mongodb dep version to support ssl conns (#368) +- Increase Mongo compatability to 2.4 + +# 0.8.1 / 2016-05-08 + +- Add Node v6 to CI +- 1. Update dev dependencies for out of date. 2. Small fix to job.js for invalid repeatAt +- Update .npmignore +- Fix doc: cb not marked as optional (closes #279) +- Including nextRunAt check in query for on the fly lock. +- Picking up any job with an expired lock (not just recurring or queued). +- Fixed failing test +- throw on processJobResult error +- Requeuing concurrency blocked jobs wrt priority. +- Processing the next job that is not blocked by concurrency. +- Fix test which fails only sometimes +- Add agendash as alternative ui +- Merge pull request #288 from diesal11/master + +# 0.8.0 / 2016-02-21 + +- Implementing lock limit +- Use callback to handle errors if we can. + +# 0.7.9 / 2016-02-05 + +- fix: ReferenceError: MongoError is not defined + +# 0.7.8 / 2016-02-03 + +- fix: computeNextRunAt timezone bug + +# 0.7.7 / 2016-01-25 + +- feat: add timezone option for repeatAt. +- fix: job locking logic +- fix: bug with jobs expiring and being enqueued anyway +- fix: bug where jobs wouldn't run concurrently +- fix: agenda throwing an exception when starting a job defined on another instance +- fix: possible bug when using extended Array.prototype + +# 0.7.6 / 2016-01-04 + +- feat: Add failCount attribute to jobs +- fix: job priority for on the fly job lock and queueing is now respected +- fix: make agenda.cancel no longer require a callback +- fix: stale jobs running after a more up-to-date job has completed +- fix: fail/success event emit after jobs have been saved in the database +- fix: ready event when using config.mongo + +# 0.7.5 / 2015-12-05 + +- Adds options.insertOnly to job.unique that prevents the job from being updated multiple times on multiple runs + +# 0.7.4 / 2015-11-26 + +- fix job priority scheduling + +# 0.7.3 / 2015-11-22 + +- add support for success callbacks on schedule, every and now (@mgregson) +- using self for reference to collection (@3choBoomer) +- emit ready from db_init (@jdiamond) + +# 0.7.2 / 2015-10-22 + +- Rollback job completion callback to pre-0.7.0 +- Emit events when Agenda init is ready or has failed + +# 0.7.0 / 2015-09-29 + +- Switch from mongoskin to mongodb native. Big thanks to the + [classdojo](http://classdojo.com) team for this. Shoutouts to @liamdon, + @jetzhou and @byronmwong for the help! + +# 0.6.28 / 2015-02-13 + +- Fix for when \_findAndLockNextJob returns multiple jobs. + +# 0.6.27 / 2015-02-04 + +- code cleanup, fix leaking ignoreErrors + +# 0.6.26 / 2014-11-30 + +- fix double run bug + +# 0.6.25 / 2014-11-20 + +- Allow specifying mongo config (optionally) + +# 0.6.24 / 2014-10-31 + +- Fix .every() running when using cron strings. + +# 0.6.23 / 2014-10-25 + +- Remove debugger + +# 0.6.22 / 2014-10-22 + +- add job.unique (@nwkeeley) + +# 0.6.21 / 2014-10-20 + +- Re-add tests for those who use the `npat` option. + +# 0.6.20 / 2014-10-14 + +- add job.disable() and job.enable() +- Added .npmignore for test/ build scripts. + +# 0.6.19 / 2014-09-03 + +- Create database indexes when initializing Agenda instance (@andyneville) + +# 0.6.18 / 2014-08-16 + +- Implemented job.isRunning() +- Fixed issue where jobs would continue being processed after agenda is explicitly stopped +- Fixed complete event being emitted before asynchronous jobs are finished + +# 0.6.17 / 2014-08-11 + +- add job.repeatAt + +# 0.6.16 / 2014-06-16 + +- fix job queue being processed even when agenda was stopped +- fix agenda.every method + +# 0.6.15 / 2014-06-11 + +- fix agenda.every overwriting nextRunAt [closes #70] + +# 0.6.14 / 2014-06-06 + +- Added agenda.cancel function +- Fix more circumstances where jobs re-create after remove + +# 0.6.13 / 2014-06-01 + +- fix jobs resaving after remove [closes #66] +- fix jobs skipping in line from database querying + +# 0.6.12/ 2014-05-22 + +- update saveJob to allow for pre-set Ids [closes #64] + +# 0.6.11/ 2014-05-19 + +- add job.touch to reset lock lifetime [references #63] + +# 0.6.10 / 2014-05-13 + +- make job saving use agenda.\_name + +# 0.6.9 / 2014-05-13 + +- add agenda.name config method +- fix agenda.mongo not being chainable + +# 0.6.8 / 2014-05-06 + +- add graceful job unlocking to stop + +# 0.6.7 / 2014-04-21 + +- Implement, document, and test defaultLockLifetime [@shakefu] + +# 0.6.6 / 2014-04-21 + +- Bump date.js version [@psema4] + +# 0.6.5 / 2014-04-17 + +- mongoskin version bump (better support for mongodb 2.6) [@loginx] + +# 0.6.4 / 2014-04-09 + +- fix $setOnInsert with empty obj cause mongodb 2.6 complain [@inetfuture] + +# 0.6.3 / 2014-04-07 + +- fix cron-jobs executing multiple times +- fail the job if repeat interval is wrong + +# 0.6.2 / 2014-03-25 + +- fix bug that resulted in jobs scheduled in memory to always re-run +- Update mongoskin to 1.3 + +# 0.6.1 / 2014-03-24 + +- allow every and schedule to take array of job names + +# 0.6.0 / 2014-03-21 (NO BREAKING CHANGES) + +- convert to using setTimeout for precise job scheduling [closes #6] + +# 0.5.10/ 2014-03-20 + +- fix agenda.every not properly saving jobs +- improve instantiating jobs, fixes bug where certain attrs weren't loaded in + +# 0.5.9 / 2014-03-10 + +- add job#remove method + +# 0.5.8 / 2014-03-07 + +- Fixed single jobs not being saved properly [closes #38] + +# 0.5.7 / 2014-03-06 + +- fix every re-running jobs out of queue at load + +# 0.5.6 / 2014-02-18 + +- Added failing for jobs with undefined definitions +- Added agenda.purge() to remove old jobs + +# 0.5.5 / 2014-01-28 + +- added support to directly give mongoskin object, to help minimize connections + +# 0.5.4 / 2014-01-09 + +- Added start event to jobs. (@clayzermki) + +# 0.5.3 / 2014-01-06 + +- Added agenda.now method + +# 0.5.2 / 2014-01-06 + +- Added ability for job.fail to take an error + +# 0.5.1 / 2013-01-04 (Backwards compatible!) + +- Updated version of humanInterval, adding weeks and months support + +# 0.5.0 / 2013-12-19 (Backwards compatible!) + +- Added job locking mechanism, enabling support for multiple works / agenda instances (@bars3s) + +# 0.4.4 / 2013-12-13 + +- fix job.toJson method: add failReason & failedAt attrs (Broken in 0.4.3 and 0.4.2) +- fix job cb for working with 'q' promises + +# 0.4.3 / 2013-12-13 + +- fix job.schedule's taking Date object as 'when' argument [@bars3s] + +# 0.4.2 / 2013-12-11 + +- Refactored Job to ensure that everything is stored as an ISODate in the Database. [Closes #14] [@raisch] + +# 0.4.1 / 2013-12-10 + +- Added support for synchronous job definitions + +# 0.4.0 / 2013-12-04 + +- Added Cron Support [Closes #2] +- removed modella dependency + +# 0.3.1 / 2013-11-19 + +- Fix for setImmediate on Node 0.8 + +# 0.3.0 / 2013-11-19 + +- Added Events to the Event Queue [References #7] + +# 0.2.1 / 2013-11-14 + +- Fixed a bug where mongo wasn't giving updated document + +# 0.2.0 / 2013-11-07 + +- Added error for running undefined job. [Closes #4] +- Fixed critical error where new jobs are not correctly saved. + +# 0.1.3 / 2013-11-06 + +- Small Bug fix for global-namespace pollution + +# 0.1.2 / 2013-10-31 + +- Updated write concern to avoid annoying notices + +# 0.1.1 / 2013-10-28 + +- Removed unecessary UUID code + +# 0.1.0 / 2013-10-28 + +- Initial Release diff --git a/README.md b/README.md index ed6272b..3afb606 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,1283 @@ -# pulse -The modern MongoDB-powered scheduling library for Node.js +# Agenda + +

+ Agenda +

+ +

+ A light-weight job scheduling library for Node.js +

+ +This was originally a fork of agenda.js, +it differs from the original version in following points: + +- Complete rewrite in Typescript (fully typed!) +- mongodb4 driver (supports mongodb 5.x) +- Supports mongoDB sharding by name +- touch() can have an optional progress parameter (0-100) +- Bugfixes and improvements for locking & job processing (concurrency, lockLimit,..) +- Breaking change: define() config paramter moved from 2nd position to 3rd +- getRunningStats() +- automatically waits for agenda to be connected before calling any database operations +- uses a database abstraction layer behind the scene +- does not create a database index by default, you can set `ensureIndex: true` when initializing Agenda + or run manually: + +``` +db.agendaJobs.ensureIndex({ + "name" : 1, + "nextRunAt" : 1, + "priority" : -1, + "lockedAt" : 1, + "disabled" : 1 +}, "findAndLockNextJobIndex") +``` + +# Agenda offers + +- Minimal overhead. Agenda aims to keep its code base small. +- Mongo backed persistence layer. +- Promises based API. +- Scheduling with configurable priority, concurrency, repeating and persistence of job results. +- Scheduling via cron or human readable syntax. +- Event backed job queue that you can hook into. +- [Agenda-rest](https://github.com/agenda/agenda-rest): optional standalone REST API. +- [Inversify-agenda](https://github.com/lautarobock/inversify-agenda) - Some utilities for the development of agenda workers with Inversify. +- [Agendash](https://github.com/agenda/agendash): optional standalone web-interface. + +### Feature Comparison + +Since there are a few job queue solutions, here a table comparing them to help you use the one that +better suits your needs. + +| Feature | Bull | Bee | Agenda | +| :------------------------- | :-------------: | :------: | :----: | +| Backend | redis | redis | mongo | +| Priorities | ✓ | | ✓ | +| Concurrency | ✓ | ✓ | ✓ | +| Delayed jobs | ✓ | | ✓ | +| Global events | ✓ | | ✓ | +| Rate Limiter | ✓ | | | +| Pause/Resume | ✓ | | ✓ | +| Sandboxed worker | ✓ | | ✓ | +| Repeatable jobs | ✓ | | ✓ | +| Atomic ops | ✓ | ✓ | ~ | +| Persistence | ✓ | ✓ | ✓ | +| UI | ✓ | | ✓ | +| REST API | | | ✓ | +| Central (Scalable) Queue | | | ✓ | +| Supports long running jobs | | | ✓ | +| Optimized for | Jobs / Messages | Messages | Jobs | + +_Kudos for making the comparison chart goes to [Bull](https://www.npmjs.com/package/bull#feature-comparison) maintainers._ + +# Installation + +Install via NPM + + npm install @hokify/agenda + +You will also need a working [Mongo](https://www.mongodb.com/) database (v4+) to point it to. + +# Example Usage + +```js +const mongoConnectionString = 'mongodb://127.0.0.1/agenda'; + +const agenda = new Agenda({ db: { address: mongoConnectionString } }); + +// Or override the default collection name: +// const agenda = new Agenda({db: {address: mongoConnectionString, collection: 'jobCollectionName'}}); + +// or pass additional connection options: +// const agenda = new Agenda({db: {address: mongoConnectionString, collection: 'jobCollectionName', options: {ssl: true}}}); + +// or pass in an existing mongodb-native MongoClient instance +// const agenda = new Agenda({mongo: myMongoClient}); + +agenda.define('delete old users', async job => { + await User.remove({ lastLogIn: { $lt: twoDaysAgo } }); +}); + +(async function () { + // IIFE to give access to async/await + await agenda.start(); + + await agenda.every('3 minutes', 'delete old users'); + + // Alternatively, you could also do: + await agenda.every('*/3 * * * *', 'delete old users'); +})(); +``` + +```js +agenda.define( + 'send email report', + async job => { + const { to } = job.attrs.data; + await emailClient.send({ + to, + from: 'example@example.com', + subject: 'Email Report', + body: '...' + }); + }, + { priority: 'high', concurrency: 10 } +); + +(async function () { + await agenda.start(); + await agenda.schedule('in 20 minutes', 'send email report', { to: 'admin@example.com' }); +})(); +``` + +```js +(async function () { + const weeklyReport = agenda.create('send email report', { to: 'example@example.com' }); + await agenda.start(); + await weeklyReport.repeatEvery('1 week').save(); +})(); +``` + +# Full documentation + +See also https://hokify.github.io/agenda/ + +Agenda's basic control structure is an instance of an agenda. Agenda's are +mapped to a database collection and load the jobs from within. + +## Table of Contents + +- [Configuring an agenda](#configuring-an-agenda) +- [Agenda Events](#agenda-events) +- [Defining job processors](#defining-job-processors) +- [Creating jobs](#creating-jobs) +- [Managing jobs](#managing-jobs) +- [Starting the job processor](#starting-the-job-processor) +- [Multiple job processors](#multiple-job-processors) +- [Manually working with jobs](#manually-working-with-a-job) +- [Job Queue Events](#job-queue-events) +- [Frequently asked questions](#frequently-asked-questions) +- [Example Project structure](#example-project-structure) +- [Known Issues](#known-issues) +- [Debugging Issues](#debugging-issues) +- [Acknowledgements](#acknowledgements) + +## Configuring an agenda + +All configuration methods are chainable, meaning you can do something like: + +```js +const agenda = new Agenda(); +agenda + .database(...) + .processEvery('3 minutes') + ...; +``` + +Possible agenda config options: + +```ts +{ + name: string; + defaultConcurrency: number; + processEvery: number; + maxConcurrency: number; + defaultLockLimit: number; + lockLimit: number; + defaultLockLifetime: number; + ensureIndex: boolean; + sort: SortOptionObject; + db: { + collection: string; + address: string; + options: MongoClientOptions; + } + mongo: Db; +} +``` + +Agenda uses [Human Interval](http://github.com/rschmukler/human-interval) for specifying the intervals. It supports the following units: + +`seconds`, `minutes`, `hours`, `days`,`weeks`, `months` -- assumes 30 days, `years` -- assumes 365 days + +More sophisticated examples + +```js +agenda.processEvery('one minute'); +agenda.processEvery('1.5 minutes'); +agenda.processEvery('3 days and 4 hours'); +agenda.processEvery('3 days, 4 hours and 36 seconds'); +``` + +### database(url, [collectionName], [MongoClientOptions]) + +Specifies the database at the `url` specified. If no collection name is given, +`agendaJobs` is used. + +By default `useNewUrlParser` and `useUnifiedTopology` is set to `true`, + +```js +agenda.database('localhost:27017/agenda-test', 'agendaJobs'); +``` + +You can also specify it during instantiation. + +```js +const agenda = new Agenda({ + db: { address: 'localhost:27017/agenda-test', collection: 'agendaJobs' } +}); +``` + +Agenda will emit a `ready` event (see [Agenda Events](#agenda-events)) when properly connected to the database. +It is safe to call `agenda.start()` without waiting for this event, as this is handled internally. +If you're using the `db` options, or call `database`, then you may still need to listen for `ready` before saving jobs. + +### mongo(dbInstance, [collectionName]) + +Use an existing mongodb-native MongoClient/Db instance. This can help consolidate connections to a +database. You can instead use `.database` to have agenda handle connecting for you. + +You can also specify it during instantiation: + +```js +const agenda = new Agenda({ mongo: mongoClientInstance.db('agenda-test') }); +``` + +Note that MongoClient.connect() returns a mongoClientInstance since [node-mongodb-native 3.0.0](https://github.com/mongodb/node-mongodb-native/blob/master/CHANGES_3.0.0.md), while it used to return a dbInstance that could then be directly passed to agenda. + +### name(name) + +Sets the `lastModifiedBy` field to `name` in the jobs collection. +Useful if you have multiple job processors (agendas) and want to see which +job queue last ran the job. + +```js +agenda.name(os.hostname + '-' + process.pid); +``` + +You can also specify it during instantiation + +```js +const agenda = new Agenda({ name: 'test queue' }); +``` + +### processEvery(interval) + +Takes a string `interval` which can be either a traditional javascript number, +or a string such as `3 minutes` + +Specifies the frequency at which agenda will query the database looking for jobs +that need to be processed. Agenda internally uses `setTimeout` to guarantee that +jobs run at (close to ~3ms) the right time. + +Decreasing the frequency will result in fewer database queries, but more jobs +being stored in memory. + +Also worth noting is that if the job queue is shutdown, any jobs stored in memory +that haven't run will still be locked, meaning that you may have to wait for the +lock to expire. By default it is `'5 seconds'`. + +```js +agenda.processEvery('1 minute'); +``` + +You can also specify it during instantiation + +```js +const agenda = new Agenda({ processEvery: '30 seconds' }); +``` + +### maxConcurrency(number) + +Takes a `number` which specifies the max number of jobs that can be running at +any given moment. By default it is `20`. + +```js +agenda.maxConcurrency(20); +``` + +You can also specify it during instantiation + +```js +const agenda = new Agenda({ maxConcurrency: 20 }); +``` + +### defaultConcurrency(number) + +Takes a `number` which specifies the default number of a specific job that can be running at +any given moment. By default it is `5`. + +```js +agenda.defaultConcurrency(5); +``` + +You can also specify it during instantiation + +```js +const agenda = new Agenda({ defaultConcurrency: 5 }); +``` + +### lockLimit(number) + +Takes a `number` which specifies the max number jobs that can be locked at any given moment. By default it is `0` for no max. + +```js +agenda.lockLimit(0); +``` + +You can also specify it during instantiation + +```js +const agenda = new Agenda({ lockLimit: 0 }); +``` + +### defaultLockLimit(number) + +Takes a `number` which specifies the default number of a specific job that can be locked at any given moment. By default it is `0` for no max. + +```js +agenda.defaultLockLimit(0); +``` + +You can also specify it during instantiation + +```js +const agenda = new Agenda({ defaultLockLimit: 0 }); +``` + +### defaultLockLifetime(number) + +Takes a `number` which specifies the default lock lifetime in milliseconds. By +default it is 10 minutes. This can be overridden by specifying the +`lockLifetime` option to a defined job. + +A job will unlock if it is finished (ie. the returned Promise resolves/rejects +or `done` is specified in the params and `done()` is called) before the +`lockLifetime`. The lock is useful if the job crashes or times out. + +```js +agenda.defaultLockLifetime(10000); +``` + +You can also specify it during instantiation + +```js +const agenda = new Agenda({ defaultLockLifetime: 10000 }); +``` + +### sort(query) + +Takes a `query` which specifies the sort query to be used for finding and locking the next job. + +By default it is `{ nextRunAt: 1, priority: -1 }`, which obeys a first in first out approach, with respect to priority. + +### disableAutoIndex(boolean) + +Optional. Disables the automatic creation of the default index on the jobs table. +By default, Agenda creates an index to optimize its queries against Mongo while processing jobs. + +This is useful if you want to use your own index in specific use-cases. + +## Agenda Events + +An instance of an agenda will emit the following events: + +- `ready` - called when Agenda mongo connection is successfully opened and indices created. + If you're passing agenda an existing connection, you shouldn't need to listen for this, as `agenda.start()` will not resolve until indices have been created. + If you're using the `db` options, or call `database`, then you may still need to listen for the `ready` event before saving jobs. `agenda.start()` will still wait for the connection to be opened. +- `error` - called when Agenda mongo connection process has thrown an error + +```js +await agenda.start(); +``` + +## Defining Job Processors + +Before you can use a job, you must define its processing behavior. + +### define(jobName, fn, [options]) + +Defines a job with the name of `jobName`. When a job of `jobName` gets run, it +will be passed to `fn(job, done)`. To maintain asynchronous behavior, you may +either provide a Promise-returning function in `fn` _or_ provide `done` as a +second parameter to `fn`. If `done` is specified in the function signature, you +must call `done()` when you are processing the job. If your function is +synchronous or returns a Promise, you may omit `done` from the signature. + +`options` is an optional argument which can overwrite the defaults. It can take +the following: + +- `concurrency`: `number` maximum number of that job that can be running at once (per instance of agenda) +- `lockLimit`: `number` maximum number of that job that can be locked at once (per instance of agenda) +- `lockLifetime`: `number` interval in ms of how long the job stays locked for (see [multiple job processors](#multiple-job-processors) for more info). + A job will automatically unlock once a returned promise resolves/rejects (or if `done` is specified in the signature and `done()` is called). +- `priority`: `(lowest|low|normal|high|highest|number)` specifies the priority + of the job. Higher priority jobs will run first. See the priority mapping + below +- `shouldSaveResult`: `boolean` flag that specifies whether the result of the job should also be stored in the database. Defaults to false + +Priority mapping: + +``` +{ + highest: 20, + high: 10, + normal: 0, + low: -10, + lowest: -20 +} +``` + +Async Job: + +```js +agenda.define('some long running job', async job => { + const data = await doSomelengthyTask(); + await formatThatData(data); + await sendThatData(data); +}); +``` + +Async Job (using `done`): + +```js +agenda.define('some long running job', (job, done) => { + doSomelengthyTask(data => { + formatThatData(data); + sendThatData(data); + done(); + }); +}); +``` + +Sync Job: + +```js +agenda.define('say hello', job => { + console.log('Hello!'); +}); +``` + +`define()` acts like an assignment: if `define(jobName, ...)` is called multiple times (e.g. every time your script starts), the definition in the last call will overwrite the previous one. Thus, if you `define` the `jobName` only once in your code, it's safe for that call to execute multiple times. + +## Creating Jobs + +### every(interval, name, [data], [options]) + +Runs job `name` at the given `interval`. Optionally, data and options can be passed in. +Every creates a job of type `single`, which means that it will only create one +job in the database, even if that line is run multiple times. This lets you put +it in a file that may get run multiple times, such as `webserver.js` which may +reboot from time to time. + +`interval` can be a human-readable format `String`, a [cron format](https://www.npmjs.com/package/cron-parser) `String`, or a `Number`. + +`data` is an optional argument that will be passed to the processing function +under `job.attrs.data`. + +`options` is an optional argument that will be passed to [`job.repeatEvery`](#repeateveryinterval-options). +In order to use this argument, `data` must also be specified. + +Returns the `job`. + +```js +agenda.define('printAnalyticsReport', async job => { + const users = await User.doSomethingReallyIntensive(); + processUserData(users); + console.log('I print a report!'); +}); + +agenda.every('15 minutes', 'printAnalyticsReport'); +``` + +Optionally, `name` could be array of job names, which is convenient for scheduling +different jobs for same `interval`. + +```js +agenda.every('15 minutes', ['printAnalyticsReport', 'sendNotifications', 'updateUserRecords']); +``` + +In this case, `every` returns array of `jobs`. + +### schedule(when, name, [data]) + +Schedules a job to run `name` once at a given time. `when` can be a `Date` or a +`String` such as `tomorrow at 5pm`. + +`data` is an optional argument that will be passed to the processing function +under `job.attrs.data`. + +Returns the `job`. + +```js +agenda.schedule('tomorrow at noon', 'printAnalyticsReport', { userCount: 100 }); +``` + +Optionally, `name` could be array of job names, similar to the `every` method. + +```js +agenda.schedule('tomorrow at noon', [ + 'printAnalyticsReport', + 'sendNotifications', + 'updateUserRecords' +]); +``` + +In this case, `schedule` returns array of `jobs`. + +### now(name, [data]) + +Schedules a job to run `name` once immediately. + +`data` is an optional argument that will be passed to the processing function +under `job.attrs.data`. + +Returns the `job`. + +```js +agenda.now('do the hokey pokey'); +``` + +### create(jobName, data) + +Returns an instance of a `jobName` with `data`. This does _NOT_ save the job in +the database. See below to learn how to manually work with jobs. + +```js +const job = agenda.create('printAnalyticsReport', { userCount: 100 }); +await job.save(); +console.log('Job successfully saved'); +``` + +## Managing Jobs + +### jobs(mongodb-native query, mongodb-native sort, mongodb-native limit, mongodb-native skip) + +Lets you query (then sort, limit and skip the result) all of the jobs in the agenda job's database. These are full [mongodb-native](https://github.com/mongodb/node-mongodb-native) `find`, `sort`, `limit` and `skip` commands. See mongodb-native's documentation for details. + +```js +const jobs = await agenda.jobs({ name: 'printAnalyticsReport' }, { data: -1 }, 3, 1); +// Work with jobs (see below) +``` + +### cancel(mongodb-native query) + +Cancels any jobs matching the passed mongodb-native query, and removes them from the database. Returns a Promise resolving to the number of cancelled jobs, or rejecting on error. + +```js +const numRemoved = await agenda.cancel({ name: 'printAnalyticsReport' }); +``` + +This functionality can also be achieved by first retrieving all the jobs from the database using `agenda.jobs()`, looping through the resulting array and calling `job.remove()` on each. It is however preferable to use `agenda.cancel()` for this use case, as this ensures the operation is atomic. + +### disable(mongodb-native query) + +Disables any jobs matching the passed mongodb-native query, preventing any matching jobs from being run by the Job Processor. + +```js +const numDisabled = await agenda.disable({ name: 'pollExternalService' }); +``` + +Similar to `agenda.cancel()`, this functionality can be acheived with a combination of `agenda.jobs()` and `job.disable()` + +### enable(mongodb-native query) + +Enables any jobs matching the passed mongodb-native query, allowing any matching jobs to be run by the Job Processor. + +```js +const numEnabled = await agenda.enable({ name: 'pollExternalService' }); +``` + +Similar to `agenda.cancel()`, this functionality can be acheived with a combination of `agenda.jobs()` and `job.enable()` + +### purge() + +Removes all jobs in the database without defined behaviors. Useful if you change a definition name and want to remove old jobs. Returns a Promise resolving to the number of removed jobs, or rejecting on error. + +_IMPORTANT:_ Do not run this before you finish defining all of your jobs. If you do, you will nuke your database of jobs. + +```js +const numRemoved = await agenda.purge(); +``` + +## Starting the job processor + +To get agenda to start processing jobs from the database you must start it. This +will schedule an interval (based on `processEvery`) to check for new jobs and +run them. You can also stop the queue. + +### start + +Starts the job queue processing, checking [`processEvery`](#processeveryinterval) time to see if there +are new jobs. Must be called _after_ `processEvery`, and _before_ any job scheduling (e.g. `every`). + +### stop + +Stops the job queue processing. Unlocks currently running jobs. + +This can be very useful for graceful shutdowns so that currently running/grabbed jobs are abandoned so that other +job queues can grab them / they are unlocked should the job queue start again. Here is an example of how to do a graceful +shutdown. + +```js +async function graceful() { + await agenda.stop(); + process.exit(0); +} + +process.on('SIGTERM', graceful); +process.on('SIGINT', graceful); +``` + +## Multiple job processors + +Sometimes you may want to have multiple node instances / machines process from +the same queue. Agenda supports a locking mechanism to ensure that multiple +queues don't process the same job. + +You can configure the locking mechanism by specifying `lockLifetime` as an +interval when defining the job. + +```js +agenda.define( + 'someJob', + (job, cb) => { + // Do something in 10 seconds or less... + }, + { lockLifetime: 10000 } +); +``` + +This will ensure that no other job processor (this one included) attempts to run the job again +for the next 10 seconds. If you have a particularly long running job, you will want to +specify a longer lockLifetime. + +By default it is 10 minutes. Typically you shouldn't have a job that runs for 10 minutes, +so this is really insurance should the job queue crash before the job is unlocked. + +When a job is finished (i.e. the returned promise resolves/rejects or `done` is +specified in the signature and `done()` is called), it will automatically unlock. + +## Manually working with a job + +A job instance has many instance methods. All mutating methods must be followed +with a call to `await job.save()` in order to persist the changes to the database. + +### repeatEvery(interval, [options]) + +Specifies an `interval` on which the job should repeat. The job runs at the time of defining as well in configured intervals, that is "run _now_ and in intervals". + +`interval` can be a human-readable format `String`, a [cron format](https://www.npmjs.com/package/cron-parser) `String`, or a `Number`. + +`options` is an optional argument containing: + +`options.timezone`: should be a string as accepted by [moment-timezone](https://momentjs.com/timezone/) and is considered when using an interval in the cron string format. + +`options.skipImmediate`: `true` | `false` (default) Setting this `true` will skip the immediate run. The first run will occur only in configured interval. + +`options.startDate`: `Date` the first time the job runs, should be equal or after the start date. + +`options.endDate`: `Date` the job should not repeat after the endDate. The job can run on the end-date itself, but not after that. + +`options.skipDays`: `human readable string` ('2 days'). After each run, it will skip the duration of 'skipDays' + +```js +job.repeatEvery('10 minutes'); +await job.save(); +``` + +```js +job.repeatEvery('3 minutes', { + skipImmediate: true +}); +await job.save(); +``` + +```js +job.repeatEvery('0 6 * * *', { + timezone: 'America/New_York' +}); +await job.save(); +``` + +### repeatAt(time) + +Specifies a `time` when the job should repeat. [Possible values](https://github.com/matthewmueller/date#examples) + +```js +job.repeatAt('3:30pm'); +await job.save(); +``` + +### schedule(time) + +Specifies the next `time` at which the job should run. + +```js +job.schedule('tomorrow at 6pm'); +await job.save(); +``` + +### priority(priority) + +Specifies the `priority` weighting of the job. Can be a number or a string from +the above priority table. + +```js +job.priority('low'); +await job.save(); +``` + +### setShouldSaveResult(setShouldSaveResult) + +Specifies whether the result of the job should also be stored in the database. Defaults to false. + +```js +job.setShouldSaveResult(true); +await job.save(); +``` + +The data returned by the job will be available on the `result` attribute after it succeeded and got retrieved again from the database, e.g. via `agenda.jobs(...)` or through the [success job event](#agenda-events)). + +### unique(properties, [options]) + +Ensure that only one instance of this job exists with the specified properties + +`options` is an optional argument which can overwrite the defaults. It can take +the following: + +- `insertOnly`: `boolean` will prevent any properties from persisting if the job already exists. Defaults to false. + +```js +job.unique({ 'data.type': 'active', 'data.userId': '123', nextRunAt: date }); +await job.save(); +``` + +_IMPORTANT:_ To avoid high CPU usage by MongoDB, make sure to create an index on the used fields, like `data.type` and `data.userId` for the example above. + +### fail(reason) + +Sets `job.attrs.failedAt` to `now`, and sets `job.attrs.failReason` to `reason`. + +Optionally, `reason` can be an error, in which case `job.attrs.failReason` will +be set to `error.message` + +```js +job.fail('insufficient disk space'); +// or +job.fail(new Error('insufficient disk space')); +await job.save(); +``` + +### run(callback) + +Runs the given `job` and calls `callback(err, job)` upon completion. Normally +you never need to call this manually. + +```js +job.run((err, job) => { + console.log("I don't know why you would need to do this..."); +}); +``` + +### save() + +Saves the `job.attrs` into the database. Returns a Promise resolving to a Job instance, or rejecting on error. + +```js +try { + await job.save(); + cosole.log('Successfully saved job to collection'); +} catch (e) { + console.error('Error saving job to collection'); +} +``` + +### remove() + +Removes the `job` from the database. Returns a Promise resolving to the number of jobs removed, or rejecting on error. + +```js +try { + await job.remove(); + console.log('Successfully removed job from collection'); +} catch (e) { + console.error('Error removing job from collection'); +} +``` + +### disable() + +Disables the `job`. Upcoming runs won't execute. + +### enable() + +Enables the `job` if it got disabled before. Upcoming runs will execute. + +### touch() + +Resets the lock on the job. Useful to indicate that the job hasn't timed out +when you have very long running jobs. The call returns a promise that resolves +when the job's lock has been renewed. + +```js +agenda.define('super long job', async job => { + await doSomeLongTask(); + await job.touch(); + await doAnotherLongTask(); + await job.touch(); + await finishOurLongTasks(); +}); +``` + +## Job Queue Events + +An instance of an agenda will emit the following events: + +- `start` - called just before a job starts +- `start:job name` - called just before the specified job starts + +```js +agenda.on('start', job => { + console.log('Job %s starting', job.attrs.name); +}); +``` + +- `complete` - called when a job finishes, regardless of if it succeeds or fails +- `complete:job name` - called when a job finishes, regardless of if it succeeds or fails + +```js +agenda.on('complete', job => { + console.log(`Job ${job.attrs.name} finished`); +}); +``` + +- `success` - called when a job finishes successfully +- `success:job name` - called when a job finishes successfully + +```js +agenda.on('success:send email', job => { + console.log(`Sent Email Successfully to ${job.attrs.data.to}`); +}); +``` + +- `fail` - called when a job throws an error +- `fail:job name` - called when a job throws an error + +```js +agenda.on('fail:send email', (err, job) => { + console.log(`Job failed with error: ${err.message}`); +}); +``` + +## Frequently Asked Questions + +### What is the order in which jobs run? + +Jobs are run with priority in a first in first out order (so they will be run in the order they were scheduled AND with respect to highest priority). + +For example, if we have two jobs named "send-email" queued (both with the same priority), and the first job is queued at 3:00 PM and second job is queued at 3:05 PM with the same `priority` value, then the first job will run first if we start to send "send-email" jobs at 3:10 PM. However if the first job has a priority of `5` and the second job has a priority of `10`, then the second will run first (priority takes precedence) at 3:10 PM. + +The default [MongoDB sort object](https://docs.mongodb.com/manual/reference/method/cursor.sort/) is `{ nextRunAt: 1, priority: -1 }` and can be changed through the option `sort` when configuring Agenda. + +### What is the difference between `lockLimit` and `maxConcurrency`? + +Agenda will lock jobs 1 by one, setting the `lockedAt` property in mongoDB, and creating an instance of the `Job` class which it caches into the `_lockedJobs` array. This defaults to having no limit, but can be managed using lockLimit. If all jobs will need to be run before agenda's next interval (set via `agenda.processEvery`), then agenda will attempt to lock all jobs. + +Agenda will also pull jobs from `_lockedJobs` and into `_runningJobs`. These jobs are actively being worked on by user code, and this is limited by `maxConcurrency` (defaults to 20). + +If you have multiple instances of agenda processing the same job definition with a fast repeat time you may find they get unevenly loaded. This is because they will compete to lock as many jobs as possible, even if they don't have enough concurrency to process them. This can be resolved by tweaking the `maxConcurrency` and `lockLimit` properties. + +### Sample Project Structure? + +Agenda doesn't have a preferred project structure and leaves it to the user to +choose how they would like to use it. That being said, you can check out the +[example project structure](#example-project-structure) below. + +### Can I Donate? + +Thanks! I'm flattered, but it's really not necessary. If you really want to, you can find my [gittip here](https://www.gittip.com/rschmukler/). + +### Web Interface? + +Agenda itself does not have a web interface built in but we do offer stand-alone web interface [Agendash](https://github.com/agenda/agendash): + +Agendash interface + +### Mongo vs Redis + +The decision to use Mongo instead of Redis is intentional. Redis is often used for +non-essential data (such as sessions) and without configuration doesn't +guarantee the same level of persistence as Mongo (should the server need to be +restarted/crash). + +Agenda decides to focus on persistence without requiring special configuration +of Redis (thereby degrading the performance of the Redis server on non-critical +data, such as sessions). + +Ultimately if enough people want a Redis driver instead of Mongo, I will write +one. (Please open an issue requesting it). For now, Agenda decided to focus on +guaranteed persistence. + +### Spawning / forking processes + +Ultimately Agenda can work from a single job queue across multiple machines, node processes, or forks. If you are interested in having more than one worker, [Bars3s](http://github.com/bars3s) has written up a fantastic example of how one might do it: + +```js +const cluster = require('cluster'); +const os = require('os'); + +const httpServer = require('./app/http-server'); +const jobWorker = require('./app/job-worker'); + +const jobWorkers = []; +const webWorkers = []; + +if (cluster.isMaster) { + const cpuCount = os.cpus().length; + // Create a worker for each CPU + for (let i = 0; i < cpuCount; i += 1) { + addJobWorker(); + addWebWorker(); + } + + cluster.on('exit', (worker, code, signal) => { + if (jobWorkers.indexOf(worker.id) !== -1) { + console.log( + `job worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...` + ); + removeJobWorker(worker.id); + addJobWorker(); + } + + if (webWorkers.indexOf(worker.id) !== -1) { + console.log( + `http worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...` + ); + removeWebWorker(worker.id); + addWebWorker(); + } + }); +} else { + if (process.env.web) { + console.log(`start http server: ${cluster.worker.id}`); + // Initialize the http server here + httpServer.start(); + } + + if (process.env.job) { + console.log(`start job server: ${cluster.worker.id}`); + // Initialize the Agenda here + jobWorker.start(); + } +} + +function addWebWorker() { + webWorkers.push(cluster.fork({ web: 1 }).id); +} + +function addJobWorker() { + jobWorkers.push(cluster.fork({ job: 1 }).id); +} + +function removeWebWorker(id) { + webWorkers.splice(webWorkers.indexOf(id), 1); +} + +function removeJobWorker(id) { + jobWorkers.splice(jobWorkers.indexOf(id), 1); +} +``` + +### Recovering lost Mongo connections ("auto_reconnect") + +Agenda is configured by default to automatically reconnect indefinitely, emitting an [error event](#agenda-events) +when no connection is available on each [process tick](#processeveryinterval), allowing you to restore the Mongo +instance without having to restart the application. + +However, if you are using an [existing Mongo client](#mongomongoclientinstance) +you'll need to configure the `reconnectTries` and `reconnectInterval` [connection settings](http://mongodb.github.io/node-mongodb-native/3.0/reference/connecting/connection-settings/) +manually, otherwise you'll find that Agenda will throw an error with the message "MongoDB connection is not recoverable, +application restart required" if the connection cannot be recovered within 30 seconds. + +# Example Project Structure + +Agenda will only process jobs that it has definitions for. This allows you to +selectively choose which jobs a given agenda will process. + +Consider the following project structure, which allows us to share models with +the rest of our code base, and specify which jobs a worker processes, if any at +all. + +``` +- server.js +- worker.js +lib/ + - agenda.js + controllers/ + - user-controller.js + jobs/ + - email.js + - video-processing.js + - image-processing.js + models/ + - user-model.js + - blog-post.model.js +``` + +Sample job processor (eg. `jobs/email.js`) + +```js +let email = require('some-email-lib'), + User = require('../models/user-model.js'); + +module.exports = function (agenda) { + agenda.define('registration email', async job => { + const user = await User.get(job.attrs.data.userId); + await email(user.email(), 'Thanks for registering', 'Thanks for registering ' + user.name()); + }); + + agenda.define('reset password', async job => { + // Etc + }); + + // More email related jobs +}; +``` + +lib/agenda.js + +```js +const Agenda = require('agenda'); + +const connectionOpts = { db: { address: 'localhost:27017/agenda-test', collection: 'agendaJobs' } }; + +const agenda = new Agenda(connectionOpts); + +const jobTypes = process.env.JOB_TYPES ? process.env.JOB_TYPES.split(',') : []; + +jobTypes.forEach(type => { + require('./jobs/' + type)(agenda); +}); + +if (jobTypes.length) { + agenda.start(); // Returns a promise, which should be handled appropriately +} + +module.exports = agenda; +``` + +lib/controllers/user-controller.js + +```js +let app = express(), + User = require('../models/user-model'), + agenda = require('../worker.js'); + +app.post('/users', (req, res, next) => { + const user = new User(req.body); + user.save(err => { + if (err) { + return next(err); + } + agenda.now('registration email', { userId: user.primary() }); + res.send(201, user.toJson()); + }); +}); +``` + +worker.js + +```js +require('./lib/agenda.js'); +``` + +Now you can do the following in your project: + +```bash +node server.js +``` + +Fire up an instance with no `JOB_TYPES`, giving you the ability to process jobs, +but not wasting resources processing jobs. + +```bash +JOB_TYPES=email node server.js +``` + +Allow your http server to process email jobs. + +```bash +JOB_TYPES=email node worker.js +``` + +Fire up an instance that processes email jobs. + +```bash +JOB_TYPES=video-processing,image-processing node worker.js +``` + +Fire up an instance that processes video-processing/image-processing jobs. Good for a heavy hitting server. + +# Debugging Issues + +If you think you have encountered a bug, please feel free to report it here: + +[Submit Issue](https://github.com/hokify/agenda/issues/new) + +Please provide us with as much details as possible such as: + +- Agenda version +- Environment (OSX, Linux, Windows, etc) +- Small description of what happened +- Any relevant stack track +- Agenda logs (see below) + +#### To turn on logging, please set your DEBUG env variable like so: + +- OSX: `DEBUG="agenda:*" ts-node src/index.ts` +- Linux: `DEBUG="agenda:*" ts-node src/index.ts` +- Windows CMD: `set DEBUG=agenda:*` +- Windows PowerShell: `$env:DEBUG = "agenda:*"` + +While not necessary, attaching a text file with this debug information would +be extremely useful in debugging certain issues and is encouraged. + +# Known Issues + +#### "Multiple order-by items are not supported. Please specify a single order-by item." + +When running Agenda on Azure cosmosDB, you might run into this issue caused by Agenda's sort query used for finding and locking the next job. To fix this, you can pass [custom sort option](#sortquery): `sort: { nextRunAt: 1 }` + +# Performance + +It is recommended to set this index if you use agendash: + +``` +db.agendaJobs.ensureIndex({ + "nextRunAt" : -1, + "lastRunAt" : -1, + "lastFinishedAt" : -1 +}, "agendash2") +``` + +If you have one job definition with thousand of instances, you can add this index to improve internal sorting query +for faster sortings + +``` +db.agendaJobs.ensureIndex({ + "name" : 1, + "disabled" : 1, + "lockedAt" : 1 +}, "findAndLockDeadJobs") +``` + +# Sandboxed Worker - use child processes + +It's possible to start jobs in a child process, this helps for example for long running processes +to seperate them from the main thread. For example if one process consumes too much memory and gets killed, +it will not affect any others. +To use this feature, several steps are required. +1.) create a childWorker helper. +The subrocess has a complete seperate context, so there are no database connections or anything else that can be shared. +Therefore you have to ensure that all required connections and initializations are done here too. Furthermore +you also have to load the correct job definition so that agenda nows what code it must execute. Therefore 3 parameters +are passed to the childWorker: name, jobId and path to the job definition. + +Example file can look like this: + +childWorker.ts + +```ts +import 'reflect-metadata'; + +process.on('message', message => { + if (message === 'cancel') { + process.exit(2); + } else { + console.log('got message', message); + } +}); + +(async () => { + const mongooseConnection = /** connect to database */ + + /** do other required initializations */ + + // get process arguments (name, jobId and path to agenda definition file) + const [, , name, jobId, agendaDefinition] = process.argv; + + // set fancy process title + process.title = `${process.title} (sub worker: ${name}/${jobId})`; + + // initialize Agenda in "forkedWorker" mode + const agenda = new Agenda({ name: `subworker-${name}`, forkedWorker: true }); + // connect agenda (but do not start it) + await agenda.mongo(mongooseConnection.db as any); + + if (!name || !jobId) { + throw new Error(`invalid parameters: ${JSON.stringify(process.argv)}`); + } + + // load job definition + /** in this case the file is for example ../some/path/definitions.js + with a content like: + export default (agenda: Agenda, definitionOnly = false) => { + agenda.define( + 'some job', + async (notification: { + attrs: { data: { dealId: string; orderId: TypeObjectId } }; + }) => { + // do something + } + ); + + if (!definitionOnly) { + // here you can create scheduled jobs or other things + } + }); + */ + if (agendaDefinition) { + const loadDefinition = await import(agendaDefinition); + (loadDefinition.default || loadDefinition)(agenda, true); + } + + // run this job now + await agenda.runForkedJob(jobId); + + // disconnect database and exit + process.exit(0); +})().catch(err => { + console.error('err', err); + if (process.send) { + process.send(JSON.stringify(err)); + } + process.exit(1); +}); + + +``` + +Ensure to only define job definitions during this step, otherwise you create some +overhead (e.g. if you create new jobs inside the defintion files). That's why I call +the defintion file with agenda and a second paramter that is set to true. If this +parameter is true, I do not initialize any jobs (create jobs etc..) + +2.) to use this, you have to enable it on a job. Set forkMode to true: + +```ts +const job = agenda.create('some job', { meep: 1 }); +job.forkMode(true); +await job.save(); +``` + +# Acknowledgements + +- Agenda was originally created by [@rschmukler](https://github.com/rschmukler). +- [Agendash](https://github.com/agenda/agendash) was originally created by [@joeframbach](https://github.com/joeframbach). +- These days Agenda has a great community of [contributors](https://github.com/hokify/agenda/graphs/contributors) around it. Join us! + +# License + +[The MIT License](LICENSE.md) diff --git a/agendats.png b/agendats.png new file mode 100644 index 0000000000000000000000000000000000000000..7b37f80316ff5e950423ff9a6d58543d71d49c7b GIT binary patch literal 6119 zcma)AcT^MKwmu;=1*w7{Di{zH5s1>IN&xA_M4CvoL69Q7Mr;TIMi51sR3Y>hdg#4M z5u^nKi4X`Ny$CPQzP-;r`+TpB475%hKYbhka68YnX?pgP#jpvWl)s3{()dgJLUG6_Z-vi(xHV~_Hvq|+#i}kGx7*~TA8}?m_ zf;k(P+mPtVlp9%G7M_xo8%&?1gf59khB!4eM77|ag_q>BDE-};HgF?U0CuhGV|2QZV%vyPKtOHy zn%K&d;7DcZ2k?3kj=rl+rLSc}zsNd2TUU1b$aU}iPJ&N&IunPAyOrB+#FLZ`nm*?E zg&{?^k7i;Xg#!(_4;`CtH5;P_FIl%&uGq{Xk%b)KCjSYD^wVI;U-(k z=P-`WLiRM2NXw0|t=V&jDVf%=4>$5Tc;vZ_p3+w?FdRK2PFn1C9Fx7o!%$^?hGb96 zxI`22X*PiWy1^{%stVN9Z`?W?dM!D9WXxiN@fVR^Jb(OCF~c1ScxLz#-KpPUc&nZC zbo<&~rWjilo!`jBl*PVWAQ%`z{ic_bqt#@Jsimw@l6t2Ol?E4A7#^K)PaE***rK|H z-cDBw0ltDGVB&RoA&uH|%=M=E6RHPC{vMDU7vp`XoeUn@`Wg)LP!7&3XX3Bsx&y#9 zr;SiU`iw142727Nm&mZXveK&;FO_{|;0E1TNJwc}=_B{#_|hd+(c8ZAHpKcCl>Dnr ztEZkV-p1z0leShZo+y^%gxu;^lcML;?pj=;9hjTazSKUDAA9PN`NPXbV?v3-*S5=p zyGt`uIuyOHMt2Jl`{d7jn(Ixh?3*C@CzGZ~%BKWi|6r-2LdM*WD|ubIMU|WF1aW%s z2G;BO^@I4XHo-hAI79sx^KJx}&f4{QWpE~z=WBBd5U>Mm>`+W74?r{n0T?}Chl9{i zh$?~^!2Z*%_aXytR0Gd@$-@ucwP+&a_KHVaO#TrN40gIssIxJ#T=5YiA-Q-oR5aRg zhcM47#)$q9zkTnREr)mMayHj>SEUuWsrL84Mg?7M)EI^qK4_bPdQ$97;fF$ zo9g-d@6jC5$qtv;*#E&I{ITa)BR4I=l@tEbWE5S<=@Jdahig^wGV)3_H9*Dgr5=CZ z;12klPmz}E5u7|!hW>9({)ObHRTN-A%=w}w*W@b3hIx9^IE$v8+kB?DFv8N656g2BGf=a?uG$$;kAEwau=W4tGHkIA+4* zn5UUhO0)ed0}CFlo>tZfiqWujrb)IHH@T9zzUYFXC-yNgWmzLt0%fyi#}6XW9dypl z&T*jy{;U|;lV+J`>oaW+gWTcC=BV>9Xct|P8FTB$L_mSMKG4_inbbhv5{jG+&_n4D zjl8g}kc-osLiYBVe)}5|&5P{MyEWEHeaXoD%v$*To|)KI4DRl!jNh!MGGWEc_2n84 zUr4?WqDsE~c*MYRrUIRS?$QS046Y9p5~pT-ryg*N-!U`IOgqxXEpqeDT^n53AjU{1 zbl&NA(p()|*xcN7xvYLmC9dZ_KXla`-0Cd)irecX#9dQpwF)+#v*=rI9e-m~g52bz z`XN}V^^V!gO>4z>`S{S>>`w3yjZR?s?D;&$-13K15I^R`1%)51BXj#5_-cx>97`W> zViu0SdB=kWL*%h1l=y8fnjKSrg5WYkjl}+5e`5if?TX$5&9Ud!DBNQVeEtv2oezVE z^A@6W?UKtAG8&dBEiKh++41}tdG>nH?J%8@(NQ_3`T;E7$ZKh|D0h)Y6=iBE=JnY~ z_H%yzW8Y(h^R;rRvSItr@@$z`XnVILb3x;d*FtaN^`i`0h6WV3=Mts`_YH{x ze_zgp<>C%MES=ELij9rsL~hD}BI7aNAi3Vl49HE#zV*8*z2$4O`Q*Eb$E`+_E%LvJ zF%%Tvq3?FMdMfOu{?nGb^sCqA#5q~UPuvua=D)IKa+BvJkpmm2QR9&uI+24LN6bms z@|^QH^Pmc6a0)w!@;r|jq_3TCIXpbP-er{Dhg(b*Jd@$ybw?#Ld+`eh$fqGwX2IV2 zQBo@I8e(qLj>QZ1dy)#yUqL}(ODoX%)~&%J&|A#-X4@U?jY}y^jLO-|_;RuKe88`niF@#qB!b?l+sdmSrNTdrl%zI6g=|8V{NMoOckVtGyu`Nh*Z>Y(r4 zRQTo49a?v2ZSFqtJ9>)~9A0uC9CN#EK2$)U%tnnXdTu8f0Frqrjm|l6VTDFYN=gs1 z$`VeqIN>!i$)Mrzq_PSEb4N92-qKP$7p%)ht0Iovh175VAq5iaE-;4twB<>nPTii5 za*o*Q7>l*B@$qe1wbgI|Mn#Z(gM!G*i|VePKIqW!IFH`?eq}dDBVKIlC%cuwnKE!w zh=XJodcO48yFKiI3V+eXZj^CfrwkRstzlhQ%^a(=wDe^e=dleP`1s`T@MiP0pdpw4 zu)E07XZZY_=vclaV+bEgTF12qM=bN#7z#8*OgS27^LKoh739uIT=q@M-T{S*YhOPY zd>LOq!t+x652zvf4!b9_wnIl(#jTZq2d=(DMF+E^-jF4{*<%ijgz0!|Puw+Pqy6smI?>}C$oafk02Dy|_|!O+ll_$A47Gi_UtM+@Z?D`;< zTy+A^M9s2ou&pNy*E8|>tuYUUKrRd?lNcW-)(Ok^+d}$G+wObZ)~JUmBq2#pnCxs3 z+Tl>FoM=!tX_pxFc&f&C1=CsEkUH)f2EVV(MjNktn0iliB5H|}t_ znvYu-Q_~L2Z7--FM&_bsQ!xjnTJN(j8qHu}IlNK_`;S@EI*eWoSG#>TGZla+;F zFIZB)@Y-`TsI8i1ctXOQ4+VpJ{@8|(B>nQe8?Mn3&yn(uKy!L&^r)$1}*4m;$_dM?OPr_3k^1hSaIgg4xLx9)sB{M9W)^gP(7K9fr6^?)H z?|+M#ys+9dauhWn=EG5$^6N#y6m{`0w4)KY@w<#w+E|l~))E}F*qx=*LdZu(iw_z| z&RBp~r|%Z>ua>m)Tw8r-V@T)nAjbE^%d=t(omTYeMIezs6V{))C6cRM>0N116L_q?mb>fhC)JH_#ud`OLg>1Ev>5j|e271~!$C38 zV4UUDUGy7Y(Hmr^tU$XIlJ9x|ktmZ1!90X=MoN4=vD=_~M0U!5VCu&aJ=Czk;w9D6Ob0bKtgscZgPWp>p z1&V#wzvAfxV3rKlRmwl+R9#lSN6vPK_Ag)Th;iIaX&8xk1VY<8i*{%<9IjyUZMl7! zF+Fd)wby(CE?|QBOG{i@*z9Q)`{HwO!!o}zkOanXT|7?V!6HS8U-Ly0ZFe81?Jyln zQ(;;?q2m4<8T+1_P#XjZ+!=AES#5=n?dTp4xD*e2-tkHmpNG*xZl3GX*`j05uE-JF zeEZJ1a{NdFh{e3DYI$%xP4eIS!~2p;@&3yaH`;GOOGb( zYDp+>TxUKtQRTENo$t`}rq)$&_us+i^-c!;HvTRbS>o4C1GZ+9y2=DucvT52-e8p7 z){5wDHB#tjSw>?5`|Z@#@&(zi<^2Xe0^NDVX!RnQtAfe3hsR}b41Nsst@i>+mp8IO z6^~UJr}2#BPvWp`K6`Pk-QeQ82W&i}kg*!?Y7*op zhJA@>*fP83bi6d=ww6znIJ>VxTjqT3KjLOl82}N1v zrUzG;9Is#sm^ZfruRXY_7Z?3l>Z4ja=4;Y%mrb&VYBO@7AjrBqI>QUp_P?Y-^r4--RZ(*1+m|XE=d}U9&(X`V0t7MpD{Q!MbJVW zmFcJ)9^n=*5LOCCYkW(FE3gN@s(0}&Y~d|o$1ztd@ik#Hud4a6w!3ZE2puq5RWRz3 zK`qxVjP{L%Niyf<>-=>wONb4MAib)Zy)BHId^VwOc!Vz~aOpxEdy0Uq^@u9;6&oTb zY}j?`uIjB$21WJxqaphuvzSZ$$!BMp3;8;cvJgbg@&1X4fIB=dUFdDDM@`iV=IYgP zvFF^62u*-alksHgH-^! zQ{=W~KyZO(V3U5lOzn>!jILY?xz`ojgLZKt+FNa*>km=t>gup_QOUwEk(@<7(5#Vu zu09B*50#_tMh_*KlXLR&0wbe11pk)^kqU3aVmwAuHNqE#4U+8PQ z90#AbW&S8{j(D@OkDX5>Qz(QRh#E-U7=w-e;q8Tnq^=`xYbPi2FqOm=npNMq2Zq9U zkkuL5&h;#afDY{pejdRmd!H)KX;@iJ+JR(~;Xh*f)DdA)($YOp*G^1l-Q@P4o3}EY zCo163Y0X$+OMrB?u9;0xTz!C=>^89l{mpAtYAV|$H+g`RTGryU{PND65IxX83A^gk z`jg(iTV9Np+5~EBHHZbcv!zfV)&UUdbDF^Fcy5YLtcp1n*A2O`kP~kq?)x|V(1C+1nv?hBc zTfM7cfjoV^{4RIXUQ$vLDFRM3tMXVNn};w1oI7EKh%z;mHAG2pVXLwJ9DS~l(A79S z;q@4D)r!NhVkTA87@gdw4BZdkUfPawytKWBxd0o;!hK;Ws-W?)rfyS6<&EI88yirs zA&Rk^XXE&xg|7X{tYPf|#O>r6*(Pxi(f5Q-Vvlh2On{O5b^9rXKfcAdpxBIN@OjOU zsyloNJ#g(`k#P7b2_B{{$wjUws@3^DBl)%!J5Z^EczAfA)gKr4HzA&XpyLHn?4@{o zYh>}!l6?pD0h(VF%Hw9NDRv++G11@b>hKHMKs}>;a?&MO@Q0g11e3Xg!(_gV^*`W7 ztQgftXE}f}wTeu-+8U5kRJ4Zd$_$Qx&+o&E-Hncpw8TvQMnRUN5+;XQql!epox!&p zM@yObz*e^er3DX#YtCb(3)EQk0HlqLjXNwhTc3v~X7&$V%s)}ty!(oO<2)?1?MZcO z%%xlXM3oJ+EzF(NB~f>9mz%#DAT_9|Qyts}JICD1_cb@gFxz{CI>-I3Wd`$9G{ngB z*m@r@3tnqE6+FH0CE4q2n>mXO9J31yR4EOgK_P$rUDTv7Eb5i5kGHAmh@hPNRrY^P zTb$I_`?OVBQIP(>N|>3IoN2BES+kb8f~OKrL?Gj0p`#D0B^hYg^=+sU*MB7s{+kqr z^iAzyO#j8O$`KxsWw%+v_#cF?R*Bc88-M&niz`C@>ST5myq6uSmw$Yv(0Bh30;a_= zuSJpEbu>0-K>{o0A&os?`~P*ye*;3*6jM7noizm-nAj#-|Ehjrr_2};|75dMN6??b WTwzHQ5|^nGFwoX8K;+-B3HcwulzeFb literal 0 HcmV?d00001 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..2f7efbe --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file diff --git a/docs/agenda/6.x/.nojekyll b/docs/agenda/6.x/.nojekyll new file mode 100644 index 0000000..e2ac661 --- /dev/null +++ b/docs/agenda/6.x/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/agenda/6.x/assets/highlight.css b/docs/agenda/6.x/assets/highlight.css new file mode 100644 index 0000000..92ac88c --- /dev/null +++ b/docs/agenda/6.x/assets/highlight.css @@ -0,0 +1,113 @@ +:root { + --light-hl-0: #001080; + --dark-hl-0: #9CDCFE; + --light-hl-1: #000000; + --dark-hl-1: #D4D4D4; + --light-hl-2: #795E26; + --dark-hl-2: #DCDCAA; + --light-hl-3: #A31515; + --dark-hl-3: #CE9178; + --light-hl-4: #098658; + --dark-hl-4: #B5CEA8; + --light-hl-5: #0000FF; + --dark-hl-5: #569CD6; + --light-hl-6: #0070C1; + --dark-hl-6: #4FC1FF; + --light-hl-7: #008000; + --dark-hl-7: #6A9955; + --light-hl-8: #AF00DB; + --dark-hl-8: #C586C0; + --light-hl-9: #000000; + --dark-hl-9: #C8C8C8; + --light-hl-10: #267F99; + --dark-hl-10: #4EC9B0; + --light-hl-11: #EE0000; + --dark-hl-11: #D7BA7D; + --light-hl-12: #000000FF; + --dark-hl-12: #D4D4D4; + --light-code-background: #F5F5F5; + --dark-code-background: #1E1E1E; +} + +@media (prefers-color-scheme: light) { :root { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --hl-8: var(--light-hl-8); + --hl-9: var(--light-hl-9); + --hl-10: var(--light-hl-10); + --hl-11: var(--light-hl-11); + --hl-12: var(--light-hl-12); + --code-background: var(--light-code-background); +} } + +@media (prefers-color-scheme: dark) { :root { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --hl-8: var(--dark-hl-8); + --hl-9: var(--dark-hl-9); + --hl-10: var(--dark-hl-10); + --hl-11: var(--dark-hl-11); + --hl-12: var(--dark-hl-12); + --code-background: var(--dark-code-background); +} } + +body.light { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --hl-8: var(--light-hl-8); + --hl-9: var(--light-hl-9); + --hl-10: var(--light-hl-10); + --hl-11: var(--light-hl-11); + --hl-12: var(--light-hl-12); + --code-background: var(--light-code-background); +} + +body.dark { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --hl-8: var(--dark-hl-8); + --hl-9: var(--dark-hl-9); + --hl-10: var(--dark-hl-10); + --hl-11: var(--dark-hl-11); + --hl-12: var(--dark-hl-12); + --code-background: var(--dark-code-background); +} + +.hl-0 { color: var(--hl-0); } +.hl-1 { color: var(--hl-1); } +.hl-2 { color: var(--hl-2); } +.hl-3 { color: var(--hl-3); } +.hl-4 { color: var(--hl-4); } +.hl-5 { color: var(--hl-5); } +.hl-6 { color: var(--hl-6); } +.hl-7 { color: var(--hl-7); } +.hl-8 { color: var(--hl-8); } +.hl-9 { color: var(--hl-9); } +.hl-10 { color: var(--hl-10); } +.hl-11 { color: var(--hl-11); } +.hl-12 { color: var(--hl-12); } +pre, code { background: var(--code-background); } diff --git a/docs/agenda/6.x/assets/icons.css b/docs/agenda/6.x/assets/icons.css new file mode 100644 index 0000000..776a356 --- /dev/null +++ b/docs/agenda/6.x/assets/icons.css @@ -0,0 +1,1043 @@ +.tsd-kind-icon { + display: block; + position: relative; + padding-left: 20px; + text-indent: -20px; +} +.tsd-kind-icon:before { + content: ""; + display: inline-block; + vertical-align: middle; + width: 17px; + height: 17px; + margin: 0 3px 2px 0; + background-image: url(./icons.png); +} +@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { + .tsd-kind-icon:before { + background-image: url(./icons@2x.png); + background-size: 238px 204px; + } +} + +.tsd-signature.tsd-kind-icon:before { + background-position: 0 -153px; +} + +.tsd-kind-object-literal > .tsd-kind-icon:before { + background-position: 0px -17px; +} +.tsd-kind-object-literal.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -17px; +} +.tsd-kind-object-literal.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -17px; +} + +.tsd-kind-class > .tsd-kind-icon:before { + background-position: 0px -34px; +} +.tsd-kind-class.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -34px; +} +.tsd-kind-class.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -34px; +} + +.tsd-kind-class.tsd-has-type-parameter > .tsd-kind-icon:before { + background-position: 0px -51px; +} +.tsd-kind-class.tsd-has-type-parameter.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -17px -51px; +} +.tsd-kind-class.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -51px; +} + +.tsd-kind-interface > .tsd-kind-icon:before { + background-position: 0px -68px; +} +.tsd-kind-interface.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -68px; +} +.tsd-kind-interface.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -68px; +} + +.tsd-kind-interface.tsd-has-type-parameter > .tsd-kind-icon:before { + background-position: 0px -85px; +} +.tsd-kind-interface.tsd-has-type-parameter.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -17px -85px; +} +.tsd-kind-interface.tsd-has-type-parameter.tsd-is-private + > .tsd-kind-icon:before { + background-position: -34px -85px; +} + +.tsd-kind-namespace > .tsd-kind-icon:before { + background-position: 0px -102px; +} +.tsd-kind-namespace.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -102px; +} +.tsd-kind-namespace.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -102px; +} + +.tsd-kind-module > .tsd-kind-icon:before { + background-position: 0px -102px; +} +.tsd-kind-module.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -102px; +} +.tsd-kind-module.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -102px; +} + +.tsd-kind-enum > .tsd-kind-icon:before { + background-position: 0px -119px; +} +.tsd-kind-enum.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -119px; +} +.tsd-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -119px; +} + +.tsd-kind-enum-member > .tsd-kind-icon:before { + background-position: 0px -136px; +} +.tsd-kind-enum-member.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -136px; +} +.tsd-kind-enum-member.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -136px; +} + +.tsd-kind-signature > .tsd-kind-icon:before { + background-position: 0px -153px; +} +.tsd-kind-signature.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -153px; +} +.tsd-kind-signature.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -153px; +} + +.tsd-kind-type-alias > .tsd-kind-icon:before { + background-position: 0px -170px; +} +.tsd-kind-type-alias.tsd-is-protected > .tsd-kind-icon:before { + background-position: -17px -170px; +} +.tsd-kind-type-alias.tsd-is-private > .tsd-kind-icon:before { + background-position: -34px -170px; +} + +.tsd-kind-type-alias.tsd-has-type-parameter > .tsd-kind-icon:before { + background-position: 0px -187px; +} +.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -17px -187px; +} +.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-private + > .tsd-kind-icon:before { + background-position: -34px -187px; +} + +.tsd-kind-variable > .tsd-kind-icon:before { + background-position: -136px -0px; +} +.tsd-kind-variable.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -0px; +} +.tsd-kind-variable.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -0px; +} +.tsd-kind-variable.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -0px; +} +.tsd-kind-variable.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -0px; +} +.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -0px; +} +.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -0px; +} +.tsd-kind-variable.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -0px; +} +.tsd-kind-variable.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -0px; +} +.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -0px; +} +.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -0px; +} +.tsd-kind-variable.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -0px; +} +.tsd-kind-variable.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -0px; +} + +.tsd-kind-property > .tsd-kind-icon:before { + background-position: -136px -0px; +} +.tsd-kind-property.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -0px; +} +.tsd-kind-property.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -0px; +} +.tsd-kind-property.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -0px; +} +.tsd-kind-property.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -0px; +} +.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -0px; +} +.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -0px; +} +.tsd-kind-property.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -0px; +} +.tsd-kind-property.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -0px; +} +.tsd-kind-property.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -0px; +} +.tsd-kind-property.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -0px; +} +.tsd-kind-property.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -0px; +} +.tsd-kind-property.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -0px; +} + +.tsd-kind-get-signature > .tsd-kind-icon:before { + background-position: -136px -17px; +} +.tsd-kind-get-signature.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -17px; +} +.tsd-kind-get-signature.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -17px; +} +.tsd-kind-get-signature.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -17px; +} + +.tsd-kind-set-signature > .tsd-kind-icon:before { + background-position: -136px -34px; +} +.tsd-kind-set-signature.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -34px; +} +.tsd-kind-set-signature.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -34px; +} +.tsd-kind-set-signature.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -34px; +} + +.tsd-kind-accessor > .tsd-kind-icon:before { + background-position: -136px -51px; +} +.tsd-kind-accessor.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -51px; +} +.tsd-kind-accessor.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -51px; +} +.tsd-kind-accessor.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -51px; +} + +.tsd-kind-function > .tsd-kind-icon:before { + background-position: -136px -68px; +} +.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -68px; +} +.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -68px; +} +.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -68px; +} +.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -68px; +} +.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -68px; +} +.tsd-kind-function.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -68px; +} +.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -68px; +} +.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-function.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -68px; +} +.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -68px; +} + +.tsd-kind-method > .tsd-kind-icon:before { + background-position: -136px -68px; +} +.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -68px; +} +.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -68px; +} +.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -68px; +} +.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -68px; +} +.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -68px; +} +.tsd-kind-method.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -68px; +} +.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { + background-position: -187px -68px; +} +.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-method.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -68px; +} +.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -68px; +} + +.tsd-kind-call-signature > .tsd-kind-icon:before { + background-position: -136px -68px; +} +.tsd-kind-call-signature.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -68px; +} +.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -68px; +} +.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -68px; +} + +.tsd-kind-function.tsd-has-type-parameter > .tsd-kind-icon:before { + background-position: -136px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -153px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class + > .tsd-kind-icon:before { + background-position: -51px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum + > .tsd-kind-icon:before { + background-position: -170px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface + > .tsd-kind-icon:before { + background-position: -204px -85px; +} +.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -85px; +} + +.tsd-kind-method.tsd-has-type-parameter > .tsd-kind-icon:before { + background-position: -136px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -153px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class + > .tsd-kind-icon:before { + background-position: -51px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum + > .tsd-kind-icon:before { + background-position: -170px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface + > .tsd-kind-icon:before { + background-position: -204px -85px; +} +.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -85px; +} + +.tsd-kind-constructor > .tsd-kind-icon:before { + background-position: -136px -102px; +} +.tsd-kind-constructor.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -102px; +} +.tsd-kind-constructor.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -102px; +} +.tsd-kind-constructor.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -102px; +} + +.tsd-kind-constructor-signature > .tsd-kind-icon:before { + background-position: -136px -102px; +} +.tsd-kind-constructor-signature.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -102px; +} +.tsd-kind-constructor-signature.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-interface + > .tsd-kind-icon:before { + background-position: -204px -102px; +} +.tsd-kind-constructor-signature.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -102px; +} + +.tsd-kind-index-signature > .tsd-kind-icon:before { + background-position: -136px -119px; +} +.tsd-kind-index-signature.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -119px; +} +.tsd-kind-index-signature.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -119px; +} +.tsd-kind-index-signature.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -119px; +} + +.tsd-kind-event > .tsd-kind-icon:before { + background-position: -136px -136px; +} +.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -136px; +} +.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -136px; +} +.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -136px; +} +.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { + background-position: -68px -136px; +} +.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { + background-position: -85px -136px; +} +.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -136px; +} +.tsd-kind-event.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -136px; +} +.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -136px; +} +.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { + background-position: -187px -136px; +} +.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -136px; +} +.tsd-kind-event.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -136px; +} +.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -136px; +} + +.tsd-is-static > .tsd-kind-icon:before { + background-position: -136px -153px; +} +.tsd-is-static.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -153px; +} +.tsd-is-static.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -153px; +} +.tsd-is-static.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -153px; +} +.tsd-is-static.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { + background-position: -68px -153px; +} +.tsd-is-static.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { + background-position: -85px -153px; +} +.tsd-is-static.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -153px; +} +.tsd-is-static.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -153px; +} +.tsd-is-static.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -153px; +} +.tsd-is-static.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { + background-position: -187px -153px; +} +.tsd-is-static.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -153px; +} +.tsd-is-static.tsd-parent-kind-interface > .tsd-kind-icon:before { + background-position: -204px -153px; +} +.tsd-is-static.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -153px; +} + +.tsd-is-static.tsd-kind-function > .tsd-kind-icon:before { + background-position: -136px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface + > .tsd-kind-icon:before { + background-position: -204px -170px; +} +.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -170px; +} + +.tsd-is-static.tsd-kind-method > .tsd-kind-icon:before { + background-position: -136px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface + > .tsd-kind-icon:before { + background-position: -204px -170px; +} +.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -170px; +} + +.tsd-is-static.tsd-kind-call-signature > .tsd-kind-icon:before { + background-position: -136px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -153px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class + > .tsd-kind-icon:before { + background-position: -51px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum + > .tsd-kind-icon:before { + background-position: -170px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface + > .tsd-kind-icon:before { + background-position: -204px -170px; +} +.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -170px; +} + +.tsd-is-static.tsd-kind-event > .tsd-kind-icon:before { + background-position: -136px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { + background-position: -153px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { + background-position: -119px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { + background-position: -51px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -68px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -85px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -102px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { + background-position: -170px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected + > .tsd-kind-icon:before { + background-position: -187px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private + > .tsd-kind-icon:before { + background-position: -119px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface + > .tsd-kind-icon:before { + background-position: -204px -187px; +} +.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited + > .tsd-kind-icon:before { + background-position: -221px -187px; +} diff --git a/docs/agenda/6.x/assets/icons.png b/docs/agenda/6.x/assets/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..3836d5fe46e48bbe186116855aae879c23935327 GIT binary patch literal 9615 zcmZ{Kc_36>+`rwViHMAd#!?~-${LfgP1$7)F~(N1WKRsT#$-?;yNq3ylq}iztr1xY z8DtsBI<`UHtDfii{r-60Kg@OSJ?GqW=bZ2NvwY{NzOLpergKbGR8*&KBGn9m;|lQC z2Vwv|y`nSufCHVQijE2uRauuTeKZL;=kiiF^SbTk;N^?*u%}Y7bF;O-aMK0lXm4nb zvU~Kf+x|Kgl@Ro%nu?L%x8-yetd((kCqY|t;-%}@Y3Ez_m(HTRt=ekeUQ2n4-aRvJ zrlKaWct8JSc8Kxl4KHu+3VW1L`9%n~_KC5}g6&tFXqyKT-}R0?EdkYqCmQot47^9Z z6;opqR@7Nq-s|6=e6*0^`}+X1kg>CpuGnbpL7{xFTa|8nymC0{xgx*tI7n4mTKZNA znsd@3eVsV>YhATuv~+5(^Vu4j?)Tn`{x@8ijIA;wdf`+0P3$vnSrcWFXXc{Lx`1Z7 z%-n(BM(owD$7LzqJx)(f^Cusecq>OW z=h6n4YzSVM-V!-DK(sLT`!W~}($=O$9|ie`>_fpH0=1G1tiIFw($?~{5T>`74|p0H z``5=UydE)!CiFvmECW|s^TzG9*7pN|KknkVm3C{fEu30gffX&8iCm? zTFPm6*k%Hog`Q6JGj@dg9Z5nlAc6ApUe>;6xauB0-u!?wMU92jVL|3EcP9gEu5^wH z%tXRy#>HCEs*?KgMf73UcJ!lJ?x<6+)eJ{mEIS|HMDP7(7!(< z@X;?ACT8mncW9*XIaiJPW}Mw@b0W||)!sYnLw)0j4&-rXQgJhnQ2?frg1Nfk&JpmV8F=dDZl)e%#Grs|&0th7_o) z?7hQn<1078qcq?#;)CH=2kBBiGt37EtcXfpTXtHB59dr9=B~jI`yPm-Q?(ys=ajAu zGY;eS^z&WFvztZI3I~}*l}_lI^}6D<&CZ94;|&G9_pMx!C~$~EL4^8`QjT#|tqxxk zhl4CdxppbDiOk!Ht#SVAK4gf6Cr#=U&1sVxZ`y-X zTSi#@wHf(?(Dd6ypNOyshRZ*tneVP^W?y?$ur_!9iD-vY{&Q5(ooX2;`SkUjwEYA~ zwGcylCT4_`MZobm(0v$U(IhfYXxyjNJ@ztpH0sDmfpn|LMp3eM(R4uqKi_q1=D1-d z%GdV<&2+_9k@sc44xhIjqktRA2!Su|vzM0R-@#MK&{RdLoU#$Hc?{{JItvX{hKCtc zQNqZpkfG^@LGJRZM4H_>`F=N;O*+_`>M_ko_XWCgu@}ntqLX8VSeZQ_25Z8|^!d?o z$~}~9|`ZW9d_o<=8&K^~;Cr08b;qgq{(*e*sNt00lO2lZ;m-b<`Rl}=Lr6iQ8+$&br z!RLn{5a}j1Dh^|_1)Q?<;iBSrS0V|c_D@3}mc2d!%tV1VN?BC@clkFdx?HB&9KOTF z)9eHpmUEYsCqx^%JHuNdwY zz9P3oPYuTAXZVY}LRp&2qNl$pbsXL1GJ@wx?@CTO!acs+OFfW_U6?&As-(GJED}RR zO}B+Kxph7aUUm>i3rbPZQGXN}oQq;u`yTnFDAJ*d$4gjEJH!JPyt6V{cOUp*Jbyol zE$8wh)T=vpJOWRbv}HvR(cUSlO}ePIPdJ`J@yp=IC&E6K%r?QfW7F&%p!H~@?%yj5 z&MpiV!hyfukD56A097f!0+ANt`JSB~oLak75oKQN7FH=rQbX#Eak37|4&mqp@S~TA zOo51)xQxX}5NQ(3I_UeR4B;P0Q#x$_lDce78ET`Blo;`Hj*R;b8slZS7Oak(LjDuE z3z?-~-U@vWe*cEOsf^9|duH9};Pe)!=Ky+QQ!jr2VV-jMUH-F>oB>Ds zDJw}jm%V?OT^fu1y`$`yRdaW03L?)6vmInxhAsGrPhWIP8?=speMFf9Inn4^t zs$!88*B~c1A2J6t0~hgK2BJ_Pl23l=oeQQqjI2(4Mcv6U_#9#$PEN|qz36rCZ5$@I zNF1LpRe%ZG4qwuYr7ZdaynrPs?spt;9VbQM$462zbksMVhAOqPunrR7@Nbv#5;VKk zJB7xC?~QXd(e9REiLixHxRGhLcKR#0va}|LMS`AXKGOIGFKQv?=+>zf^ zN5XLjX6^`zh*%1UG_QV1H`@z!HZgC+OT2`+_B( z)J95hk;3C+K4XCswSP}au;fx=47~*$k`RAaYEU-qb03y0#x|&>LAeiXgri5E(!h9k z|9OVt@sk1-4+>0?ELyw|zs`~<95M=%o?Gix$?8z4Gz3Kpw|b>?BcD&s{X)-aXg!GJ zyq&`ZEP{K^u7ActXP$gGnO#F0Sr+QUZe0&d5*Yhw9A?C4(Sx2j3QKAlUpkQz7nji^ z%y8F|W{ypj(T%Bf#Wgyvq4szMo?*U-;3IGBRg1fK9!h-=YRsZ_+t~2!-)=pr;)Vnk zmt95&wMb02toOf`I9>M^Kv3LqKb_-#jauF&cGrWsCnMt?p7*uh zevugda={D04DB#7wR375=1i5}Z9fi3r)!F#7qmX9`SjppE&%8l8bKt+ADRMTWRv21 z4L&PldV8YpHw3b^`p0uWlIm#J&K65-y4lQW0VzZR!4#gfeT{b#fL1e*)Z*Ux}M^}bO%OM7uXip_4! zL@yo@q{utZeVV?3CtXs}i>nI|%26fwuzt0f#96fQ!{=dEX^YKnvIk*D%y9Cin;9R) zi{?)baJhgFs$1$SOZESTpldw2H&FD=v*v@1cA!`|s;avDKHa>Q+uJ8qhy!9%C4&lJSTN4OeydYOm4S?Bj7*e{xRYbU9Xos)R7qZT3dBBD5{ zo+(E3pR{>>)}hFhE+}!yYP0V+CVhyAq+RV{^X`XA3{iXj(ir$k@u|t8ZJ1ZnHq2dd zD$0RHmGJ=!?T5`*T2zOEJ~y}Nsyt7O)%+!0ulRQdsopJJxoznfpusv=2@zLXIq@^& z>0T5k4lzGCG(DnltLIe@6=ZOG@C(dvmYXfh4IhJfMfY8S?KkT znb7~EDE}Yhg$J1LxB7m`L4VMS(+(SXTQvh_mz!x&M3-6Z zFRB*a%_gVEqI^mL5|c%V=l_oi%|~h>gL0SB4QH5uonWd#={KPg6}6ES)zk0~#3^KJ zJq@{iqbHe3gyC))jeQ`W;(u3|q)JxuF24|GMsh%v5>>VY-bok%* z1Yl@(5G2UCK=fQck}pAyWV0n{`ML|rsl_N7vmW|frii__zB;ozrQ7{z)y}M^Sg@m_ z;+?{q3sUZs3WxnBbp~CyyL(TA?C*0KIeDPp7w0$!Ijd+M8#}r~vYW)NB*$mG*7-vH z@s^wK07OMxq>WveCEQFQ*p&2gjD1j%i+#G9z##Th`gew>H5=`RwyfPDg2G%f>x3@c z14Oy}pQK?(i06GWLWu%4cGjDoE-tTEI$`9^E?nLT663vu_>6K1e!N>A-^q&tfl$0& zy&>w~+yUelAa!c@xd8iyt^`B^$cj+}h}0i!40K2Ve1KFCDezBzZO8@=k&r)`TNTJ* zzF4Pim>SYL^=~7kW>EyiVHXNMT2)8l#v^IW!pLB_8ZvVfK&m8QHkjsZ)mvd?o$VYG zX#HiWwWlW>N{D85URJ-d)}_3h73|)X=E(6hFzi#TF{$4aSka4TeY>1a_(RIkFBL#O zE0_FoSQI)}+si51ufAqRHhDU=actTRQl@y#2h}xaDv-A&GP&0Qu9V4ED5aWnX z1E#mRT1QSvL!4~%Ozt84nP{&F>VIm6w2q!EPhh^BF-94$4JhCTcrdbDXA3Q&8mPTh zqdPv|X}??B?bIZPpl}z%(zr<8U-NoXjb*L#xyqHHfpIGAgN$5i(E9#rYPYq_tISC4 z2TDkd*uZ;CIhVI2o!||T)Kz`ER@%rTf-&SfmJFF>;d(RW(B6k!1<)uxHM_1G+9BWe zc)k`gBxYMcztqY5@jccaU)CqQ@^G5TBVx(nNf2}D@);3+{D)GzyT{>%dO6ibggS({N!!=P4=M8J}5R*&fgd(w36z0M0D$ z(SN5a`i%sZ9vmaEjiC4)DF}ix&`?mc-vYwK@+}8Gqzj6r6y)lT|Iqwlpj(LXqvh;- zb>jECiiOZ%&Q7gQg7(ix-?-RE*c(O6NG0F-+VCr;701@%L~fyfHnU<;Vk`m3A2{1MSmpii@G*k?KDq0GdZ)|hd`8OHep z8@6wv_|9NKNpe*sc#?zZ1S#}*qk{k<(I99u6(QT#>wf9w^u9~9_>;2d20T=^g-;b5 ze9x~fHZ-JL=J`hq-;W{2SgN)&m9RsVo=%?`JYp`pxEA_>`18Y>XA$rfWm^pQfG3MQ zxT^I1*({tZz2}+!5$AyNUE*jiYwu_S8v<#qZS4e!bGGBdY`3RkgLMf%Kz8s-;7PF+ z6w#-FwV#)PiKGR79miXmrDyv=ZTjc)j>N=&h4F+#G;unBZhhZz?a*;8@bi5`fV4)O zuU5pCs;tvRzbV@P5%W5xLI4I+w*^KExeVlzP4kNRGp-wi3g$lf-I|(o`JQ|u^XfkP zcik+g-5~2lG*oHfjLCpfNalFwz=4ZY>$Rc-QGpws&tCfFZUuJDL)3et%ap*$Q=-v0 zgLfsn-&%#+wnox~@)6ppx30sK(UJg1dCAvQF&}DkoPI+uX_wH))iaYvWtl}BtVKpU&MN= z0GdENbhdLgIwL-#_phGK;mZRlk4zq8*)akvV5zRX@jFUmvcr#3p99P@4z@m|bz-)^ zbZl8Wt?hR*z(sEZl;2PaILIG#835i@YoZQ@EwrD9IOBl7BpJX(ilLgcd)KCZAzo^b z6Z{|~=H;$D2dD53tejr_jx7^y-zT{SNZpNjn4+wJQX~K#LcrlKOv=D5xk%QXD{tg; z+xh`PvMV*HC*rF?xyjK5@KsMl5*w`r@wL#r13uFpso~#^oYIFc^&gGNS825eqFttU2_sG%_ z;X8VXD#Ol4X&$2B_Z$*&-)ZIUXf9I%mOOXJ3O%GbGpJfl+9(jY^fF_(b!Gt{{HAA3 zusUOCPDHYT@&*H~7a050c7r-_CaFACp$BXx)5==@fC11Gn|n~~+u@6N-}lvdyl3&6 z<#c_zm0Xp1F!8o2OBbFfgzzC4vno}9XEf40dGaVo;jiwiazo8hZ~iPVD(re=5k;H| zotm286$6nnTeIw>1FY$Ri|t{Lp?o(Fg3g_>|y~Z+16tvyLc@r?t9g7 zBuXyVuu9bC#q`?@OFIhgS)6v^XP@H0ukl2X!RPMsg%`YHMGad z4{VsgxaprFss3X%HbZablb6IdaNdbISVWp7yQXPPn=s7?J9qLEH{4>XAv8}%h&TDg zs()1sh}4at3nL3^%q!?P9BbW80e*ZwU63}CV7pt}gVu;~V6c$9p+*wfhw!zeE-z|V z=k{Ksec2)$Hu&?pRh;*TPk0T$Fc~^oAoBT4q?-Q}Y&3DluXeoMQ0LesTk}pVlf5(I z$dl8;zA0&=L&z*F*H>W7IeiPhTo@P0VTB~vyC2Bm7lCN}t7@NNlKFSHGKkh?z_qij zoYju!#D4b28cdslLdIM5Cmqe&!v^IcRr=qq^?l+P^n@6}fh@)IS81hx)SPAY7osk0)^ulqC1F*{hBNQl+Y}b>XjVXnS_Cc!L zIZ@Jq#mp^E&fKT~t4DM_^S17R@YJ@`(7;zv1mz_Y=~q*Gdg#*yXGxotY=#F|lvhPM zjlE)VHS=8=)njE^c7M|ZiBqARx>9Ib!y91$70iC8jPi$c+ysP}5Q3s`ti&1sx>~oG zI^>^1onS%G`mtq&)cZ15dZ{X^#MOfatyH0I=l%Q)n z7*@kZtC_3?=J_}?_G@?F?UK<0_AhYFclyrS-PkfYhAeVHcF z16x+quy10*2V$A%p_|@C(vlf}j3uY83h(#TSr$(;^8(I={_=YQQWmA9-IlwJv>tQm z=vN-I{TO7X`;qBxwb5w$91YLV?ZD5}pddq(7IdMCH zi>`qAn|#FITi!L5;K!(tYm9r416}Wof}P8~?R9I9Gp(?VA;uQg19MO47*gS7fH*&jBO!+ zA*<^BMccHjJIvGHguBb4a`X z3aZw#!c&Xr8&szD1+gu&;vYfoWo>0Pxfr2%m34tC33fmRbzWF9I_Pqb9nNK@N##9_ z7K)v)des!^owH`MoXY_O?|;^9;comiPx0e78xhnnVvTYt+t+cU1rn_>gaFJsL-iPn)?<9P9cF#4)7q&v+d&6|3G@s-AcJy+m zE&u*GUaMK|x|4GmT(CgBICk`2BP@3rqtjKIRD#uBy}y*d;<>`?W&mGsG;i*_}V&^tlP`%;=g39@jxP z+3lrtg*!i6N;irOpUfKcd;iDl5a`<#kr8RwFm9=^m+ouwwjcXmTB}w5V#9IF^&Bl$ zr1$Ly#cQ<3u86>am9}pk&i%nxu(W&s@>qEDtn_xVtH-_EiQ}iAK4Ssfsdn&L9t=)d z`XOQN7*J)g$Jrtq0=-yeLnHg*23LxYA7$cxz^Yc)I6E-!;{LQwu_wfGw4&MYy7{n< z@{g0Hf)N5gAJKQ1Z&HGPn9x9B7U(m(9K&=+LHAc_D{YdMBZs~x)u1Y8|Oq!`C4(3_9<&$ddi6>R$Nsz z*ti?=jA-Sr_97V}feo+}Lq3-cfpgWR;PLI8s{ve9@?e;2o}0MpquOucipz^DrT}QH z*(<{nLb4h9799hx4&%I8KPj}xcQ}llgcaG1!nRb(PP?m)=CzA4v%6>oOe96H9 zv4mUhw`>V$29k?)$Co>qIqq(~3w4jJ;Hv5(RxjB-j_iEhlF;&|DDC|I8IcT>Vn;RY zhtw5mT0ygXAu=M%{^;GqYuYIMu4H;Mj--5CL}|zMEhOum_o51Y7i|D>$XmUFoe;@1 z%GsTUsKgF4w%-Cr3lg#~h)8;Lk%WQTLBS8r*sE{YBUDw4HU#o}E)8pVIEfWv&14?U z-+Za${OFm=>IA358en)nB5Iaqxw&Xi*ty@uDOX8o2c0tq0^sX>ZXD+Hn|;KY!Omm1 z^%wgf&Zy9Azd?vmU`~zuOOA0{TZ*mAC!_>|avcN83F#c+sFn_6tGo!v?95IUR2bL$ zlO(OlhszqAgy)mNt8PRulC#6u^SL#z-O&@{=_!AzBZ>T4ROorj%fx$A;u8u>saum0ha7p zeHRX-z)PW*@v9bruyAtVI@)PhaEs5kp`xyxTQ`U9$Whwz#z$=U$V|&0w@EfCUS!Ob zACSTE{VeC-0V~ZCpkKq~P4CLgdOeBy>vB+0ZxIt_Cp4aa%vI#LS^K}ui07WNo}5r0 zagMHmq-jqTf-OD<kAvu_ob1mUP%1jxeKqB!1&-)_hP{p74hHE%WM!atyx68j5b zSqwh8aKo|NIOL<2_eiX+iOsRP`{MUt{0iQetB*SL!F_8)_;0f$iJ4(o__4KWuvy_! z8TZ{dTb*rL6VmuN-yl2Z>0glL84u^jAH^DQl}VRI=x0CnuF*|;|My-5aPI;>(mo+m z`nyEOe&k$RG11$vEdDPG7^raBCw|#C*4#pIUoZJNx?4|ZC{)l>+jaSiiJ`GBKf}l) zUk1>%A61hqy!KvfRsM^|u6vwbH5WpfH(I5AdpBAg%rar%zW}nccGxfgRV4&v`tEoGyBq!uz^f zVqWEtxn%j&+Q2Fi$rL)H`M_HExP+?mFyN^){c{JXs{IM}f}p>7lfD zLZ;s)%6a(Ow@`(jP}k~pn@!dv6JhJkZf5UoumHv`g-tcCs)w* z#0sc%t9@Li{p}f*$vg$UiQ*RGZUr=ykDIaxRDU_(QfcURuYrpX*7IQcS$(Buw%VW7 zxaffDgn{-=K@iEh)LlPc3MPzc+qM^>RXr6Y8ASnP&dr6fqmwYILTpmh$E%{Iz%Qz( NZmR35l_G4O{0}dcmS_L~ literal 0 HcmV?d00001 diff --git a/docs/agenda/6.x/assets/icons@2x.png b/docs/agenda/6.x/assets/icons@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5a209e2f6d7f915cc9cb6fe7a4264c8be4db87b0 GIT binary patch literal 28144 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 Ce=Object.create;var J=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,_e=Object.prototype.hasOwnProperty;var Me=t=>J(t,"__esModule",{value:!0});var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Oe(e))!_e.call(t,i)&&(r||i!=="default")&&J(t,i,{get:()=>e[i],enumerable:!(n=Pe(e,i))||n.enumerable});return t},Ae=(t,e)=>De(Me(J(t!=null?Ce(Re(t)):{},"default",!e&&t&&t.__esModule?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var de=Fe((ce,he)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(r+=n[l+1]*i[h+1],l+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),p=s.str.charAt(1),v;p in s.node.edges?v=s.node.edges[p]:(v=new t.TokenSet,s.node.edges[p]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ce=="object"?he.exports=r():e.lunr=r()}(this,function(){return t})})()});var le=[];function N(t,e){le.push({selector:e,constructor:t})}var X=class{constructor(){this.createComponents(document.body)}createComponents(e){le.forEach(r=>{e.querySelectorAll(r.selector).forEach(n=>{n.dataset.hasInstance||(new r.constructor({el:n}),n.dataset.hasInstance=String(!0))})})}};var Q=class{constructor(e){this.el=e.el}};var Z=class{constructor(){this.listeners={}}addEventListener(e,r){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(r)}removeEventListener(e,r){if(!(e in this.listeners))return;let n=this.listeners[e];for(let i=0,s=n.length;i{let r=Date.now();return(...n)=>{r+e-Date.now()<0&&(t(...n),r=Date.now())}};var ee=class extends Z{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.secondaryNav=document.querySelector(".tsd-navigation.secondary"),window.addEventListener("scroll",K(()=>this.onScroll(),10)),window.addEventListener("resize",K(()=>this.onResize(),10)),this.onResize(),this.onScroll()}triggerResize(){let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onScroll(){this.scrollTop=window.scrollY||0;let e=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(e),this.hideShowToolbar()}hideShowToolbar(){var r;let e=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0,e!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),(r=this.secondaryNav)==null||r.classList.toggle("tsd-navigation--toolbar-hide")),this.lastY=this.scrollTop}},I=ee;I.instance=new ee;var te=class extends Q{constructor(e){super(e);this.anchors=[];this.index=-1;I.instance.addEventListener("resize",()=>this.onResize()),I.instance.addEventListener("scroll",r=>this.onScroll(r)),this.createAnchors()}createAnchors(){let e=window.location.href;e.indexOf("#")!=-1&&(e=e.substr(0,e.indexOf("#"))),this.el.querySelectorAll("a").forEach(r=>{let n=r.href;if(n.indexOf("#")==-1||n.substr(0,e.length)!=e)return;let i=n.substr(n.indexOf("#")+1),s=document.querySelector("a.tsd-anchor[name="+i+"]"),o=r.parentNode;!s||!o||this.anchors.push({link:o,anchor:s,position:0})}),this.onResize()}onResize(){let e;for(let n=0,i=this.anchors.length;nn.position-i.position);let r=new CustomEvent("scroll",{detail:{scrollTop:I.instance.scrollTop}});this.onScroll(r)}onScroll(e){let r=e.detail.scrollTop+5,n=this.anchors,i=n.length-1,s=this.index;for(;s>-1&&n[s].position>r;)s-=1;for(;s-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=s,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var ue=(t,e=100)=>{let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>t(n),e)}};var me=Ae(de());function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let r=document.querySelector("#tsd-search input"),n=document.querySelector("#tsd-search .results");if(!r||!n)throw new Error("The input field or the result list wrapper was not found");let i=!1;n.addEventListener("mousedown",()=>i=!0),n.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Ve(t,n,r,s)}function Ve(t,e,r,n){r.addEventListener("input",ue(()=>{ze(t,e,r,n)},200));let i=!1;r.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ne(e,r):s.key=="Escape"?r.blur():s.key=="ArrowUp"?fe(e,-1):s.key==="ArrowDown"?fe(e,1):i=!1}),r.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!r.matches(":focus")&&s.key==="/"&&(r.focus(),s.preventDefault())})}function He(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=me.Index.load(window.searchData.index))}function ze(t,e,r,n){if(He(n,t),!n.index||!n.data)return;e.textContent="";let i=r.value.trim(),s=n.index.search(`*${i}*`);for(let o=0,a=Math.min(10,s.length);o${pe(u.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=u.classes;let p=document.createElement("a");p.href=n.base+u.url,p.classList.add("tsd-kind-icon"),p.innerHTML=l,h.append(p),e.appendChild(h)}}function fe(t,e){let r=t.querySelector(".current");if(!r)r=t.querySelector(e==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let n=r;if(e===1)do n=n.nextElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);else do n=n.previousElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);n&&(r.classList.remove("current"),n.classList.add("current"))}}function Ne(t,e){let r=t.querySelector(".current");if(r||(r=t.querySelector("li:first-child")),r){let n=r.querySelector("a");n&&(window.location.href=n.href),e.blur()}}function pe(t,e){if(e==="")return t;let r=t.toLocaleLowerCase(),n=e.toLocaleLowerCase(),i=[],s=0,o=r.indexOf(n);for(;o!=-1;)i.push(re(t.substring(s,o)),`${re(t.substring(o,o+n.length))}`),s=o+n.length,o=r.indexOf(n,s);return i.push(re(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function re(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var ge=class{constructor(e,r){this.signature=e,this.description=r}addClass(e){return this.signature.classList.add(e),this.description.classList.add(e),this}removeClass(e){return this.signature.classList.remove(e),this.description.classList.remove(e),this}},ne=class extends Q{constructor(e){super(e);this.groups=[];this.index=-1;this.createGroups(),this.container&&(this.el.classList.add("active"),Array.from(this.el.children).forEach(r=>{r.addEventListener("touchstart",n=>this.onClick(n)),r.addEventListener("click",n=>this.onClick(n))}),this.container.classList.add("active"),this.setIndex(0))}setIndex(e){if(e<0&&(e=0),e>this.groups.length-1&&(e=this.groups.length-1),this.index==e)return;let r=this.groups[e];if(this.index>-1){let n=this.groups[this.index];n.removeClass("current").addClass("fade-out"),r.addClass("current"),r.addClass("fade-in"),I.instance.triggerResize(),setTimeout(()=>{n.removeClass("fade-out"),r.removeClass("fade-in")},300)}else r.addClass("current"),I.instance.triggerResize();this.index=e}createGroups(){let e=this.el.children;if(e.length<2)return;this.container=this.el.nextElementSibling;let r=this.container.children;this.groups=[];for(let n=0;n{r.signature===e.currentTarget&&this.setIndex(n)})}};var C="mousedown",xe="mousemove",_="mouseup",G={x:0,y:0},ye=!1,ie=!1,Be=!1,A=!1,Le=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Le?"is-mobile":"not-mobile");Le&&"ontouchstart"in document.documentElement&&(Be=!0,C="touchstart",xe="touchmove",_="touchend");document.addEventListener(C,t=>{ie=!0,A=!1;let e=C=="touchstart"?t.targetTouches[0]:t;G.y=e.pageY||0,G.x=e.pageX||0});document.addEventListener(xe,t=>{if(!!ie&&!A){let e=C=="touchstart"?t.targetTouches[0]:t,r=G.x-(e.pageX||0),n=G.y-(e.pageY||0);A=Math.sqrt(r*r+n*n)>10}});document.addEventListener(_,()=>{ie=!1});document.addEventListener("click",t=>{ye&&(t.preventDefault(),t.stopImmediatePropagation(),ye=!1)});var se=class extends Q{constructor(e){super(e);this.className=this.el.dataset.toggle||"",this.el.addEventListener(_,r=>this.onPointerUp(r)),this.el.addEventListener("click",r=>r.preventDefault()),document.addEventListener(C,r=>this.onDocumentPointerDown(r)),document.addEventListener(_,r=>this.onDocumentPointerUp(r))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let r=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(r),setTimeout(()=>document.documentElement.classList.remove(r),500)}onPointerUp(e){A||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!A&&this.active&&e.target.closest(".col-menu")){let r=e.target.closest("a");if(r){let n=window.location.href;n.indexOf("#")!=-1&&(n=n.substr(0,n.indexOf("#"))),r.href.substr(0,n.length)==n&&setTimeout(()=>this.setActive(!1),250)}}}};var ae=class{constructor(e,r){this.key=e,this.value=r,this.defaultValue=r,this.initialize(),window.localStorage[this.key]&&this.setValue(this.fromLocalStorage(window.localStorage[this.key]))}initialize(){}setValue(e){if(this.value==e)return;let r=this.value;this.value=e,window.localStorage[this.key]=this.toLocalStorage(e),this.handleValueChange(r,e)}},oe=class extends ae{initialize(){let e=document.querySelector("#tsd-filter-"+this.key);!e||(this.checkbox=e,this.checkbox.addEventListener("change",()=>{this.setValue(this.checkbox.checked)}))}handleValueChange(e,r){!this.checkbox||(this.checkbox.checked=this.value,document.documentElement.classList.toggle("toggle-"+this.key,this.value!=this.defaultValue))}fromLocalStorage(e){return e=="true"}toLocalStorage(e){return e?"true":"false"}},Ee=class extends ae{initialize(){document.documentElement.classList.add("toggle-"+this.key+this.value);let e=document.querySelector("#tsd-filter-"+this.key);if(!e)return;this.select=e;let r=()=>{this.select.classList.add("active")},n=()=>{this.select.classList.remove("active")};this.select.addEventListener(C,r),this.select.addEventListener("mouseover",r),this.select.addEventListener("mouseleave",n),this.select.querySelectorAll("li").forEach(i=>{i.addEventListener(_,s=>{e.classList.remove("active"),this.setValue(s.target.dataset.value||"")})}),document.addEventListener(C,i=>{this.select.contains(i.target)||this.select.classList.remove("active")})}handleValueChange(e,r){this.select.querySelectorAll("li.selected").forEach(s=>{s.classList.remove("selected")});let n=this.select.querySelector('li[data-value="'+r+'"]'),i=this.select.querySelector(".tsd-select-label");n&&i&&(n.classList.add("selected"),i.textContent=n.textContent),document.documentElement.classList.remove("toggle-"+e),document.documentElement.classList.add("toggle-"+r)}fromLocalStorage(e){return e}toLocalStorage(e){return e}},Y=class extends Q{constructor(e){super(e);this.optionVisibility=new Ee("visibility","private"),this.optionInherited=new oe("inherited",!0),this.optionExternals=new oe("externals",!0)}static isSupported(){try{return typeof window.localStorage!="undefined"}catch{return!1}}};function we(t){let e=localStorage.getItem("tsd-theme")||"os";t.value=e,be(e),t.addEventListener("change",()=>{localStorage.setItem("tsd-theme",t.value),be(t.value)})}function be(t){switch(t){case"os":document.body.classList.remove("light","dark");break;case"light":document.body.classList.remove("dark"),document.body.classList.add("light");break;case"dark":document.body.classList.remove("light"),document.body.classList.add("dark");break}}ve();N(te,".menu-highlight");N(ne,".tsd-signatures");N(se,"a[data-toggle]");Y.isSupported()?N(Y,"#tsd-filter"):document.documentElement.classList.add("no-filter");var Te=document.getElementById("theme");Te&&we(Te);var qe=new X;Object.defineProperty(window,"app",{value:qe});})(); +/*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ +/*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + */ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */ diff --git a/docs/agenda/6.x/assets/search.js b/docs/agenda/6.x/assets/search.js new file mode 100644 index 0000000..adc7829 --- /dev/null +++ b/docs/agenda/6.x/assets/search.js @@ -0,0 +1 @@ +window.searchData = JSON.parse("{\"kinds\":{\"32\":\"Variable\",\"128\":\"Class\",\"256\":\"Interface\",\"512\":\"Constructor\",\"1024\":\"Property\",\"2048\":\"Method\",\"65536\":\"Type literal\",\"4194304\":\"Type alias\"},\"rows\":[{\"id\":0,\"kind\":128,\"name\":\"Agenda\",\"url\":\"classes/Agenda.html\",\"classes\":\"tsd-kind-class\"},{\"id\":1,\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Agenda.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class tsd-is-overwrite\",\"parent\":\"Agenda\"},{\"id\":2,\"kind\":1024,\"name\":\"attrs\",\"url\":\"classes/Agenda.html#attrs\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":3,\"kind\":1024,\"name\":\"db\",\"url\":\"classes/Agenda.html#db\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":4,\"kind\":2048,\"name\":\"on\",\"url\":\"classes/Agenda.html#on\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-overwrite\",\"parent\":\"Agenda\"},{\"id\":5,\"kind\":1024,\"name\":\"definitions\",\"url\":\"classes/Agenda.html#definitions\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":6,\"kind\":65536,\"name\":\"__type\",\"url\":\"classes/Agenda.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":7,\"kind\":1024,\"name\":\"jobProcessor\",\"url\":\"classes/Agenda.html#jobProcessor\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Agenda\"},{\"id\":8,\"kind\":1024,\"name\":\"ready\",\"url\":\"classes/Agenda.html#ready\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":9,\"kind\":2048,\"name\":\"isActiveJobProcessor\",\"url\":\"classes/Agenda.html#isActiveJobProcessor\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":10,\"kind\":2048,\"name\":\"getRunningStats\",\"url\":\"classes/Agenda.html#getRunningStats\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":11,\"kind\":2048,\"name\":\"database\",\"url\":\"classes/Agenda.html#database\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":12,\"kind\":2048,\"name\":\"mongo\",\"url\":\"classes/Agenda.html#mongo\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":13,\"kind\":2048,\"name\":\"sort\",\"url\":\"classes/Agenda.html#sort\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":14,\"kind\":2048,\"name\":\"hasDatabaseConfig\",\"url\":\"classes/Agenda.html#hasDatabaseConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-private\",\"parent\":\"Agenda\"},{\"id\":15,\"kind\":2048,\"name\":\"cancel\",\"url\":\"classes/Agenda.html#cancel\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":16,\"kind\":2048,\"name\":\"name\",\"url\":\"classes/Agenda.html#name\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":17,\"kind\":2048,\"name\":\"processEvery\",\"url\":\"classes/Agenda.html#processEvery\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":18,\"kind\":2048,\"name\":\"maxConcurrency\",\"url\":\"classes/Agenda.html#maxConcurrency\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":19,\"kind\":2048,\"name\":\"defaultConcurrency\",\"url\":\"classes/Agenda.html#defaultConcurrency\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":20,\"kind\":2048,\"name\":\"lockLimit\",\"url\":\"classes/Agenda.html#lockLimit\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":21,\"kind\":2048,\"name\":\"defaultLockLimit\",\"url\":\"classes/Agenda.html#defaultLockLimit\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":22,\"kind\":2048,\"name\":\"defaultLockLifetime\",\"url\":\"classes/Agenda.html#defaultLockLifetime\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":23,\"kind\":2048,\"name\":\"jobs\",\"url\":\"classes/Agenda.html#jobs\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":24,\"kind\":2048,\"name\":\"purge\",\"url\":\"classes/Agenda.html#purge\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":25,\"kind\":2048,\"name\":\"define\",\"url\":\"classes/Agenda.html#define\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter\",\"parent\":\"Agenda\"},{\"id\":26,\"kind\":2048,\"name\":\"createJobs\",\"url\":\"classes/Agenda.html#createJobs\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter tsd-is-private\",\"parent\":\"Agenda\"},{\"id\":27,\"kind\":2048,\"name\":\"create\",\"url\":\"classes/Agenda.html#create\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter\",\"parent\":\"Agenda\"},{\"id\":28,\"kind\":2048,\"name\":\"every\",\"url\":\"classes/Agenda.html#every\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter\",\"parent\":\"Agenda\"},{\"id\":29,\"kind\":2048,\"name\":\"schedule\",\"url\":\"classes/Agenda.html#schedule\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter\",\"parent\":\"Agenda\"},{\"id\":30,\"kind\":2048,\"name\":\"now\",\"url\":\"classes/Agenda.html#now\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter\",\"parent\":\"Agenda\"},{\"id\":31,\"kind\":2048,\"name\":\"start\",\"url\":\"classes/Agenda.html#start\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":32,\"kind\":2048,\"name\":\"stop\",\"url\":\"classes/Agenda.html#stop\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Agenda\"},{\"id\":33,\"kind\":256,\"name\":\"IAgendaConfig\",\"url\":\"interfaces/IAgendaConfig.html\",\"classes\":\"tsd-kind-interface\"},{\"id\":34,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/IAgendaConfig.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IAgendaConfig\"},{\"id\":35,\"kind\":1024,\"name\":\"defaultConcurrency\",\"url\":\"interfaces/IAgendaConfig.html#defaultConcurrency\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IAgendaConfig\"},{\"id\":36,\"kind\":1024,\"name\":\"processEvery\",\"url\":\"interfaces/IAgendaConfig.html#processEvery\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IAgendaConfig\"},{\"id\":37,\"kind\":1024,\"name\":\"maxConcurrency\",\"url\":\"interfaces/IAgendaConfig.html#maxConcurrency\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IAgendaConfig\"},{\"id\":38,\"kind\":1024,\"name\":\"defaultLockLimit\",\"url\":\"interfaces/IAgendaConfig.html#defaultLockLimit\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IAgendaConfig\"},{\"id\":39,\"kind\":1024,\"name\":\"lockLimit\",\"url\":\"interfaces/IAgendaConfig.html#lockLimit\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IAgendaConfig\"},{\"id\":40,\"kind\":1024,\"name\":\"defaultLockLifetime\",\"url\":\"interfaces/IAgendaConfig.html#defaultLockLifetime\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IAgendaConfig\"},{\"id\":41,\"kind\":256,\"name\":\"IJobDefinition\",\"url\":\"interfaces/IJobDefinition.html\",\"classes\":\"tsd-kind-interface tsd-has-type-parameter\"},{\"id\":42,\"kind\":1024,\"name\":\"lockLimit\",\"url\":\"interfaces/IJobDefinition.html#lockLimit\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobDefinition\"},{\"id\":43,\"kind\":1024,\"name\":\"lockLifetime\",\"url\":\"interfaces/IJobDefinition.html#lockLifetime\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobDefinition\"},{\"id\":44,\"kind\":1024,\"name\":\"priority\",\"url\":\"interfaces/IJobDefinition.html#priority\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobDefinition\"},{\"id\":45,\"kind\":1024,\"name\":\"concurrency\",\"url\":\"interfaces/IJobDefinition.html#concurrency\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobDefinition\"},{\"id\":46,\"kind\":1024,\"name\":\"fn\",\"url\":\"interfaces/IJobDefinition.html#fn\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobDefinition\"},{\"id\":47,\"kind\":4194304,\"name\":\"DefinitionProcessor\",\"url\":\"modules.html#DefinitionProcessor\",\"classes\":\"tsd-kind-type-alias tsd-has-type-parameter\"},{\"id\":48,\"kind\":65536,\"name\":\"__type\",\"url\":\"modules.html#DefinitionProcessor.__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-type-alias\",\"parent\":\"DefinitionProcessor\"},{\"id\":49,\"kind\":256,\"name\":\"IJobParameters\",\"url\":\"interfaces/IJobParameters.html\",\"classes\":\"tsd-kind-interface tsd-has-type-parameter\"},{\"id\":50,\"kind\":1024,\"name\":\"_id\",\"url\":\"interfaces/IJobParameters.html#_id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":51,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/IJobParameters.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":52,\"kind\":1024,\"name\":\"priority\",\"url\":\"interfaces/IJobParameters.html#priority\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":53,\"kind\":1024,\"name\":\"nextRunAt\",\"url\":\"interfaces/IJobParameters.html#nextRunAt\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":54,\"kind\":1024,\"name\":\"type\",\"url\":\"interfaces/IJobParameters.html#type\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":55,\"kind\":1024,\"name\":\"lockedAt\",\"url\":\"interfaces/IJobParameters.html#lockedAt\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":56,\"kind\":1024,\"name\":\"lastFinishedAt\",\"url\":\"interfaces/IJobParameters.html#lastFinishedAt\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":57,\"kind\":1024,\"name\":\"failedAt\",\"url\":\"interfaces/IJobParameters.html#failedAt\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":58,\"kind\":1024,\"name\":\"failCount\",\"url\":\"interfaces/IJobParameters.html#failCount\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":59,\"kind\":1024,\"name\":\"failReason\",\"url\":\"interfaces/IJobParameters.html#failReason\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":60,\"kind\":1024,\"name\":\"repeatTimezone\",\"url\":\"interfaces/IJobParameters.html#repeatTimezone\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":61,\"kind\":1024,\"name\":\"lastRunAt\",\"url\":\"interfaces/IJobParameters.html#lastRunAt\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":62,\"kind\":1024,\"name\":\"repeatInterval\",\"url\":\"interfaces/IJobParameters.html#repeatInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":63,\"kind\":1024,\"name\":\"data\",\"url\":\"interfaces/IJobParameters.html#data\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":64,\"kind\":1024,\"name\":\"repeatAt\",\"url\":\"interfaces/IJobParameters.html#repeatAt\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":65,\"kind\":1024,\"name\":\"disabled\",\"url\":\"interfaces/IJobParameters.html#disabled\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":66,\"kind\":1024,\"name\":\"progress\",\"url\":\"interfaces/IJobParameters.html#progress\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":67,\"kind\":1024,\"name\":\"unique\",\"url\":\"interfaces/IJobParameters.html#unique\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":68,\"kind\":1024,\"name\":\"uniqueOpts\",\"url\":\"interfaces/IJobParameters.html#uniqueOpts\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":69,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/IJobParameters.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":70,\"kind\":1024,\"name\":\"insertOnly\",\"url\":\"interfaces/IJobParameters.html#__type.insertOnly\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"IJobParameters.__type\"},{\"id\":71,\"kind\":1024,\"name\":\"lastModifiedBy\",\"url\":\"interfaces/IJobParameters.html#lastModifiedBy\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IJobParameters\"},{\"id\":72,\"kind\":4194304,\"name\":\"TJobDatefield\",\"url\":\"modules.html#TJobDatefield\",\"classes\":\"tsd-kind-type-alias\"},{\"id\":73,\"kind\":32,\"name\":\"datefields\",\"url\":\"modules.html#datefields\",\"classes\":\"tsd-kind-variable\"},{\"id\":74,\"kind\":256,\"name\":\"IDatabaseOptions\",\"url\":\"interfaces/IDatabaseOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"id\":75,\"kind\":1024,\"name\":\"db\",\"url\":\"interfaces/IDatabaseOptions.html#db\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IDatabaseOptions\"},{\"id\":76,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/IDatabaseOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"IDatabaseOptions\"},{\"id\":77,\"kind\":1024,\"name\":\"collection\",\"url\":\"interfaces/IDatabaseOptions.html#__type.collection\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"IDatabaseOptions.__type\"},{\"id\":78,\"kind\":1024,\"name\":\"address\",\"url\":\"interfaces/IDatabaseOptions.html#__type.address\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"IDatabaseOptions.__type\"},{\"id\":79,\"kind\":1024,\"name\":\"options\",\"url\":\"interfaces/IDatabaseOptions.html#__type.options\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"IDatabaseOptions.__type\"},{\"id\":80,\"kind\":256,\"name\":\"IMongoOptions\",\"url\":\"interfaces/IMongoOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"id\":81,\"kind\":1024,\"name\":\"db\",\"url\":\"interfaces/IMongoOptions.html#db\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IMongoOptions\"},{\"id\":82,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/IMongoOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"IMongoOptions\"},{\"id\":83,\"kind\":1024,\"name\":\"collection\",\"url\":\"interfaces/IMongoOptions.html#__type.collection\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"IMongoOptions.__type\"},{\"id\":84,\"kind\":1024,\"name\":\"mongo\",\"url\":\"interfaces/IMongoOptions.html#mongo\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IMongoOptions\"},{\"id\":85,\"kind\":256,\"name\":\"IDbConfig\",\"url\":\"interfaces/IDbConfig.html\",\"classes\":\"tsd-kind-interface\"},{\"id\":86,\"kind\":1024,\"name\":\"ensureIndex\",\"url\":\"interfaces/IDbConfig.html#ensureIndex\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IDbConfig\"},{\"id\":87,\"kind\":1024,\"name\":\"sort\",\"url\":\"interfaces/IDbConfig.html#sort\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"IDbConfig\"},{\"id\":88,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/IDbConfig.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"IDbConfig\"},{\"id\":89,\"kind\":128,\"name\":\"Job\",\"url\":\"classes/Job.html\",\"classes\":\"tsd-kind-class tsd-has-type-parameter\"},{\"id\":90,\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Job.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class tsd-has-type-parameter\",\"parent\":\"Job\"},{\"id\":91,\"kind\":1024,\"name\":\"attrs\",\"url\":\"classes/Job.html#attrs\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":92,\"kind\":1024,\"name\":\"canceled\",\"url\":\"classes/Job.html#canceled\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":93,\"kind\":1024,\"name\":\"gotTimerToExecute\",\"url\":\"classes/Job.html#gotTimerToExecute\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":94,\"kind\":1024,\"name\":\"agenda\",\"url\":\"classes/Job.html#agenda\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":95,\"kind\":2048,\"name\":\"toJson\",\"url\":\"classes/Job.html#toJson\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":96,\"kind\":2048,\"name\":\"repeatEvery\",\"url\":\"classes/Job.html#repeatEvery\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":97,\"kind\":2048,\"name\":\"repeatAt\",\"url\":\"classes/Job.html#repeatAt\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":98,\"kind\":2048,\"name\":\"disable\",\"url\":\"classes/Job.html#disable\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":99,\"kind\":2048,\"name\":\"enable\",\"url\":\"classes/Job.html#enable\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":100,\"kind\":2048,\"name\":\"unique\",\"url\":\"classes/Job.html#unique\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":101,\"kind\":2048,\"name\":\"schedule\",\"url\":\"classes/Job.html#schedule\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":102,\"kind\":2048,\"name\":\"priority\",\"url\":\"classes/Job.html#priority\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":103,\"kind\":2048,\"name\":\"fail\",\"url\":\"classes/Job.html#fail\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":104,\"kind\":2048,\"name\":\"fetchStatus\",\"url\":\"classes/Job.html#fetchStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-private\",\"parent\":\"Job\"},{\"id\":105,\"kind\":2048,\"name\":\"isRunning\",\"url\":\"classes/Job.html#isRunning\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":106,\"kind\":2048,\"name\":\"save\",\"url\":\"classes/Job.html#save\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":107,\"kind\":2048,\"name\":\"remove\",\"url\":\"classes/Job.html#remove\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":108,\"kind\":2048,\"name\":\"isDead\",\"url\":\"classes/Job.html#isDead\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":109,\"kind\":2048,\"name\":\"isExpired\",\"url\":\"classes/Job.html#isExpired\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":110,\"kind\":2048,\"name\":\"touch\",\"url\":\"classes/Job.html#touch\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":111,\"kind\":2048,\"name\":\"computeNextRunAt\",\"url\":\"classes/Job.html#computeNextRunAt\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-private\",\"parent\":\"Job\"},{\"id\":112,\"kind\":2048,\"name\":\"run\",\"url\":\"classes/Job.html#run\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Job\"},{\"id\":113,\"kind\":2048,\"name\":\"isPromise\",\"url\":\"classes/Job.html#isPromise\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-private\",\"parent\":\"Job\"},{\"id\":114,\"kind\":4194304,\"name\":\"JobWithId\",\"url\":\"modules.html#JobWithId\",\"classes\":\"tsd-kind-type-alias\"}],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"name\",\"parent\"],\"fieldVectors\":[[\"name/0\",[0,12.126]],[\"parent/0\",[]],[\"name/1\",[1,38.373]],[\"parent/1\",[0,1.157]],[\"name/2\",[2,38.373]],[\"parent/2\",[0,1.157]],[\"name/3\",[3,35.008]],[\"parent/3\",[0,1.157]],[\"name/4\",[4,43.481]],[\"parent/4\",[0,1.157]],[\"name/5\",[5,43.481]],[\"parent/5\",[0,1.157]],[\"name/6\",[6,28.818]],[\"parent/6\",[0,1.157]],[\"name/7\",[7,43.481]],[\"parent/7\",[0,1.157]],[\"name/8\",[8,43.481]],[\"parent/8\",[0,1.157]],[\"name/9\",[9,43.481]],[\"parent/9\",[0,1.157]],[\"name/10\",[10,43.481]],[\"parent/10\",[0,1.157]],[\"name/11\",[11,43.481]],[\"parent/11\",[0,1.157]],[\"name/12\",[12,38.373]],[\"parent/12\",[0,1.157]],[\"name/13\",[13,38.373]],[\"parent/13\",[0,1.157]],[\"name/14\",[14,43.481]],[\"parent/14\",[0,1.157]],[\"name/15\",[15,43.481]],[\"parent/15\",[0,1.157]],[\"name/16\",[16,35.008]],[\"parent/16\",[0,1.157]],[\"name/17\",[17,38.373]],[\"parent/17\",[0,1.157]],[\"name/18\",[18,38.373]],[\"parent/18\",[0,1.157]],[\"name/19\",[19,38.373]],[\"parent/19\",[0,1.157]],[\"name/20\",[20,35.008]],[\"parent/20\",[0,1.157]],[\"name/21\",[21,38.373]],[\"parent/21\",[0,1.157]],[\"name/22\",[22,38.373]],[\"parent/22\",[0,1.157]],[\"name/23\",[23,43.481]],[\"parent/23\",[0,1.157]],[\"name/24\",[24,43.481]],[\"parent/24\",[0,1.157]],[\"name/25\",[25,43.481]],[\"parent/25\",[0,1.157]],[\"name/26\",[26,43.481]],[\"parent/26\",[0,1.157]],[\"name/27\",[27,43.481]],[\"parent/27\",[0,1.157]],[\"name/28\",[28,43.481]],[\"parent/28\",[0,1.157]],[\"name/29\",[29,38.373]],[\"parent/29\",[0,1.157]],[\"name/30\",[30,43.481]],[\"parent/30\",[0,1.157]],[\"name/31\",[31,43.481]],[\"parent/31\",[0,1.157]],[\"name/32\",[32,43.481]],[\"parent/32\",[0,1.157]],[\"name/33\",[33,26.135]],[\"parent/33\",[]],[\"name/34\",[16,35.008]],[\"parent/34\",[33,2.495]],[\"name/35\",[19,38.373]],[\"parent/35\",[33,2.495]],[\"name/36\",[17,38.373]],[\"parent/36\",[33,2.495]],[\"name/37\",[18,38.373]],[\"parent/37\",[33,2.495]],[\"name/38\",[21,38.373]],[\"parent/38\",[33,2.495]],[\"name/39\",[20,35.008]],[\"parent/39\",[33,2.495]],[\"name/40\",[22,38.373]],[\"parent/40\",[33,2.495]],[\"name/41\",[34,28.818]],[\"parent/41\",[]],[\"name/42\",[20,35.008]],[\"parent/42\",[34,2.751]],[\"name/43\",[35,43.481]],[\"parent/43\",[34,2.751]],[\"name/44\",[36,35.008]],[\"parent/44\",[34,2.751]],[\"name/45\",[37,43.481]],[\"parent/45\",[34,2.751]],[\"name/46\",[38,43.481]],[\"parent/46\",[34,2.751]],[\"name/47\",[39,38.373]],[\"parent/47\",[]],[\"name/48\",[6,28.818]],[\"parent/48\",[39,3.663]],[\"name/49\",[40,16.401]],[\"parent/49\",[]],[\"name/50\",[41,43.481]],[\"parent/50\",[40,1.565]],[\"name/51\",[16,35.008]],[\"parent/51\",[40,1.565]],[\"name/52\",[36,35.008]],[\"parent/52\",[40,1.565]],[\"name/53\",[42,43.481]],[\"parent/53\",[40,1.565]],[\"name/54\",[43,43.481]],[\"parent/54\",[40,1.565]],[\"name/55\",[44,43.481]],[\"parent/55\",[40,1.565]],[\"name/56\",[45,43.481]],[\"parent/56\",[40,1.565]],[\"name/57\",[46,43.481]],[\"parent/57\",[40,1.565]],[\"name/58\",[47,43.481]],[\"parent/58\",[40,1.565]],[\"name/59\",[48,43.481]],[\"parent/59\",[40,1.565]],[\"name/60\",[49,43.481]],[\"parent/60\",[40,1.565]],[\"name/61\",[50,43.481]],[\"parent/61\",[40,1.565]],[\"name/62\",[51,43.481]],[\"parent/62\",[40,1.565]],[\"name/63\",[52,43.481]],[\"parent/63\",[40,1.565]],[\"name/64\",[53,38.373]],[\"parent/64\",[40,1.565]],[\"name/65\",[54,43.481]],[\"parent/65\",[40,1.565]],[\"name/66\",[55,43.481]],[\"parent/66\",[40,1.565]],[\"name/67\",[56,38.373]],[\"parent/67\",[40,1.565]],[\"name/68\",[57,43.481]],[\"parent/68\",[40,1.565]],[\"name/69\",[6,28.818]],[\"parent/69\",[40,1.565]],[\"name/70\",[58,43.481]],[\"parent/70\",[59,4.15]],[\"name/71\",[60,43.481]],[\"parent/71\",[40,1.565]],[\"name/72\",[61,43.481]],[\"parent/72\",[]],[\"name/73\",[62,43.481]],[\"parent/73\",[]],[\"name/74\",[63,35.008]],[\"parent/74\",[]],[\"name/75\",[3,35.008]],[\"parent/75\",[63,3.342]],[\"name/76\",[6,28.818]],[\"parent/76\",[63,3.342]],[\"name/77\",[64,38.373]],[\"parent/77\",[65,3.342]],[\"name/78\",[66,43.481]],[\"parent/78\",[65,3.342]],[\"name/79\",[67,43.481]],[\"parent/79\",[65,3.342]],[\"name/80\",[68,32.495]],[\"parent/80\",[]],[\"name/81\",[3,35.008]],[\"parent/81\",[68,3.102]],[\"name/82\",[6,28.818]],[\"parent/82\",[68,3.102]],[\"name/83\",[64,38.373]],[\"parent/83\",[69,4.15]],[\"name/84\",[12,38.373]],[\"parent/84\",[68,3.102]],[\"name/85\",[70,32.495]],[\"parent/85\",[]],[\"name/86\",[71,43.481]],[\"parent/86\",[70,3.102]],[\"name/87\",[13,38.373]],[\"parent/87\",[70,3.102]],[\"name/88\",[6,28.818]],[\"parent/88\",[70,3.102]],[\"name/89\",[72,15.149]],[\"parent/89\",[]],[\"name/90\",[1,38.373]],[\"parent/90\",[72,1.446]],[\"name/91\",[2,38.373]],[\"parent/91\",[72,1.446]],[\"name/92\",[73,43.481]],[\"parent/92\",[72,1.446]],[\"name/93\",[74,43.481]],[\"parent/93\",[72,1.446]],[\"name/94\",[0,12.126]],[\"parent/94\",[72,1.446]],[\"name/95\",[75,43.481]],[\"parent/95\",[72,1.446]],[\"name/96\",[76,43.481]],[\"parent/96\",[72,1.446]],[\"name/97\",[53,38.373]],[\"parent/97\",[72,1.446]],[\"name/98\",[77,43.481]],[\"parent/98\",[72,1.446]],[\"name/99\",[78,43.481]],[\"parent/99\",[72,1.446]],[\"name/100\",[56,38.373]],[\"parent/100\",[72,1.446]],[\"name/101\",[29,38.373]],[\"parent/101\",[72,1.446]],[\"name/102\",[36,35.008]],[\"parent/102\",[72,1.446]],[\"name/103\",[79,43.481]],[\"parent/103\",[72,1.446]],[\"name/104\",[80,43.481]],[\"parent/104\",[72,1.446]],[\"name/105\",[81,43.481]],[\"parent/105\",[72,1.446]],[\"name/106\",[82,43.481]],[\"parent/106\",[72,1.446]],[\"name/107\",[83,43.481]],[\"parent/107\",[72,1.446]],[\"name/108\",[84,43.481]],[\"parent/108\",[72,1.446]],[\"name/109\",[85,43.481]],[\"parent/109\",[72,1.446]],[\"name/110\",[86,43.481]],[\"parent/110\",[72,1.446]],[\"name/111\",[87,43.481]],[\"parent/111\",[72,1.446]],[\"name/112\",[88,43.481]],[\"parent/112\",[72,1.446]],[\"name/113\",[89,43.481]],[\"parent/113\",[72,1.446]],[\"name/114\",[90,43.481]],[\"parent/114\",[]]],\"invertedIndex\":[[\"__type\",{\"_index\":6,\"name\":{\"6\":{},\"48\":{},\"69\":{},\"76\":{},\"82\":{},\"88\":{}},\"parent\":{}}],[\"_id\",{\"_index\":41,\"name\":{\"50\":{}},\"parent\":{}}],[\"address\",{\"_index\":66,\"name\":{\"78\":{}},\"parent\":{}}],[\"agenda\",{\"_index\":0,\"name\":{\"0\":{},\"94\":{}},\"parent\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"16\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"26\":{},\"27\":{},\"28\":{},\"29\":{},\"30\":{},\"31\":{},\"32\":{}}}],[\"attrs\",{\"_index\":2,\"name\":{\"2\":{},\"91\":{}},\"parent\":{}}],[\"cancel\",{\"_index\":15,\"name\":{\"15\":{}},\"parent\":{}}],[\"canceled\",{\"_index\":73,\"name\":{\"92\":{}},\"parent\":{}}],[\"collection\",{\"_index\":64,\"name\":{\"77\":{},\"83\":{}},\"parent\":{}}],[\"computenextrunat\",{\"_index\":87,\"name\":{\"111\":{}},\"parent\":{}}],[\"concurrency\",{\"_index\":37,\"name\":{\"45\":{}},\"parent\":{}}],[\"constructor\",{\"_index\":1,\"name\":{\"1\":{},\"90\":{}},\"parent\":{}}],[\"create\",{\"_index\":27,\"name\":{\"27\":{}},\"parent\":{}}],[\"createjobs\",{\"_index\":26,\"name\":{\"26\":{}},\"parent\":{}}],[\"data\",{\"_index\":52,\"name\":{\"63\":{}},\"parent\":{}}],[\"database\",{\"_index\":11,\"name\":{\"11\":{}},\"parent\":{}}],[\"datefields\",{\"_index\":62,\"name\":{\"73\":{}},\"parent\":{}}],[\"db\",{\"_index\":3,\"name\":{\"3\":{},\"75\":{},\"81\":{}},\"parent\":{}}],[\"defaultconcurrency\",{\"_index\":19,\"name\":{\"19\":{},\"35\":{}},\"parent\":{}}],[\"defaultlocklifetime\",{\"_index\":22,\"name\":{\"22\":{},\"40\":{}},\"parent\":{}}],[\"defaultlocklimit\",{\"_index\":21,\"name\":{\"21\":{},\"38\":{}},\"parent\":{}}],[\"define\",{\"_index\":25,\"name\":{\"25\":{}},\"parent\":{}}],[\"definitionprocessor\",{\"_index\":39,\"name\":{\"47\":{}},\"parent\":{\"48\":{}}}],[\"definitions\",{\"_index\":5,\"name\":{\"5\":{}},\"parent\":{}}],[\"disable\",{\"_index\":77,\"name\":{\"98\":{}},\"parent\":{}}],[\"disabled\",{\"_index\":54,\"name\":{\"65\":{}},\"parent\":{}}],[\"enable\",{\"_index\":78,\"name\":{\"99\":{}},\"parent\":{}}],[\"ensureindex\",{\"_index\":71,\"name\":{\"86\":{}},\"parent\":{}}],[\"every\",{\"_index\":28,\"name\":{\"28\":{}},\"parent\":{}}],[\"fail\",{\"_index\":79,\"name\":{\"103\":{}},\"parent\":{}}],[\"failcount\",{\"_index\":47,\"name\":{\"58\":{}},\"parent\":{}}],[\"failedat\",{\"_index\":46,\"name\":{\"57\":{}},\"parent\":{}}],[\"failreason\",{\"_index\":48,\"name\":{\"59\":{}},\"parent\":{}}],[\"fetchstatus\",{\"_index\":80,\"name\":{\"104\":{}},\"parent\":{}}],[\"fn\",{\"_index\":38,\"name\":{\"46\":{}},\"parent\":{}}],[\"getrunningstats\",{\"_index\":10,\"name\":{\"10\":{}},\"parent\":{}}],[\"gottimertoexecute\",{\"_index\":74,\"name\":{\"93\":{}},\"parent\":{}}],[\"hasdatabaseconfig\",{\"_index\":14,\"name\":{\"14\":{}},\"parent\":{}}],[\"iagendaconfig\",{\"_index\":33,\"name\":{\"33\":{}},\"parent\":{\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"39\":{},\"40\":{}}}],[\"idatabaseoptions\",{\"_index\":63,\"name\":{\"74\":{}},\"parent\":{\"75\":{},\"76\":{}}}],[\"idatabaseoptions.__type\",{\"_index\":65,\"name\":{},\"parent\":{\"77\":{},\"78\":{},\"79\":{}}}],[\"idbconfig\",{\"_index\":70,\"name\":{\"85\":{}},\"parent\":{\"86\":{},\"87\":{},\"88\":{}}}],[\"ijobdefinition\",{\"_index\":34,\"name\":{\"41\":{}},\"parent\":{\"42\":{},\"43\":{},\"44\":{},\"45\":{},\"46\":{}}}],[\"ijobparameters\",{\"_index\":40,\"name\":{\"49\":{}},\"parent\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{},\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{},\"60\":{},\"61\":{},\"62\":{},\"63\":{},\"64\":{},\"65\":{},\"66\":{},\"67\":{},\"68\":{},\"69\":{},\"71\":{}}}],[\"ijobparameters.__type\",{\"_index\":59,\"name\":{},\"parent\":{\"70\":{}}}],[\"imongooptions\",{\"_index\":68,\"name\":{\"80\":{}},\"parent\":{\"81\":{},\"82\":{},\"84\":{}}}],[\"imongooptions.__type\",{\"_index\":69,\"name\":{},\"parent\":{\"83\":{}}}],[\"insertonly\",{\"_index\":58,\"name\":{\"70\":{}},\"parent\":{}}],[\"isactivejobprocessor\",{\"_index\":9,\"name\":{\"9\":{}},\"parent\":{}}],[\"isdead\",{\"_index\":84,\"name\":{\"108\":{}},\"parent\":{}}],[\"isexpired\",{\"_index\":85,\"name\":{\"109\":{}},\"parent\":{}}],[\"ispromise\",{\"_index\":89,\"name\":{\"113\":{}},\"parent\":{}}],[\"isrunning\",{\"_index\":81,\"name\":{\"105\":{}},\"parent\":{}}],[\"job\",{\"_index\":72,\"name\":{\"89\":{}},\"parent\":{\"90\":{},\"91\":{},\"92\":{},\"93\":{},\"94\":{},\"95\":{},\"96\":{},\"97\":{},\"98\":{},\"99\":{},\"100\":{},\"101\":{},\"102\":{},\"103\":{},\"104\":{},\"105\":{},\"106\":{},\"107\":{},\"108\":{},\"109\":{},\"110\":{},\"111\":{},\"112\":{},\"113\":{}}}],[\"jobprocessor\",{\"_index\":7,\"name\":{\"7\":{}},\"parent\":{}}],[\"jobs\",{\"_index\":23,\"name\":{\"23\":{}},\"parent\":{}}],[\"jobwithid\",{\"_index\":90,\"name\":{\"114\":{}},\"parent\":{}}],[\"lastfinishedat\",{\"_index\":45,\"name\":{\"56\":{}},\"parent\":{}}],[\"lastmodifiedby\",{\"_index\":60,\"name\":{\"71\":{}},\"parent\":{}}],[\"lastrunat\",{\"_index\":50,\"name\":{\"61\":{}},\"parent\":{}}],[\"lockedat\",{\"_index\":44,\"name\":{\"55\":{}},\"parent\":{}}],[\"locklifetime\",{\"_index\":35,\"name\":{\"43\":{}},\"parent\":{}}],[\"locklimit\",{\"_index\":20,\"name\":{\"20\":{},\"39\":{},\"42\":{}},\"parent\":{}}],[\"maxconcurrency\",{\"_index\":18,\"name\":{\"18\":{},\"37\":{}},\"parent\":{}}],[\"mongo\",{\"_index\":12,\"name\":{\"12\":{},\"84\":{}},\"parent\":{}}],[\"name\",{\"_index\":16,\"name\":{\"16\":{},\"34\":{},\"51\":{}},\"parent\":{}}],[\"nextrunat\",{\"_index\":42,\"name\":{\"53\":{}},\"parent\":{}}],[\"now\",{\"_index\":30,\"name\":{\"30\":{}},\"parent\":{}}],[\"on\",{\"_index\":4,\"name\":{\"4\":{}},\"parent\":{}}],[\"options\",{\"_index\":67,\"name\":{\"79\":{}},\"parent\":{}}],[\"priority\",{\"_index\":36,\"name\":{\"44\":{},\"52\":{},\"102\":{}},\"parent\":{}}],[\"processevery\",{\"_index\":17,\"name\":{\"17\":{},\"36\":{}},\"parent\":{}}],[\"progress\",{\"_index\":55,\"name\":{\"66\":{}},\"parent\":{}}],[\"purge\",{\"_index\":24,\"name\":{\"24\":{}},\"parent\":{}}],[\"ready\",{\"_index\":8,\"name\":{\"8\":{}},\"parent\":{}}],[\"remove\",{\"_index\":83,\"name\":{\"107\":{}},\"parent\":{}}],[\"repeatat\",{\"_index\":53,\"name\":{\"64\":{},\"97\":{}},\"parent\":{}}],[\"repeatevery\",{\"_index\":76,\"name\":{\"96\":{}},\"parent\":{}}],[\"repeatinterval\",{\"_index\":51,\"name\":{\"62\":{}},\"parent\":{}}],[\"repeattimezone\",{\"_index\":49,\"name\":{\"60\":{}},\"parent\":{}}],[\"run\",{\"_index\":88,\"name\":{\"112\":{}},\"parent\":{}}],[\"save\",{\"_index\":82,\"name\":{\"106\":{}},\"parent\":{}}],[\"schedule\",{\"_index\":29,\"name\":{\"29\":{},\"101\":{}},\"parent\":{}}],[\"sort\",{\"_index\":13,\"name\":{\"13\":{},\"87\":{}},\"parent\":{}}],[\"start\",{\"_index\":31,\"name\":{\"31\":{}},\"parent\":{}}],[\"stop\",{\"_index\":32,\"name\":{\"32\":{}},\"parent\":{}}],[\"tjobdatefield\",{\"_index\":61,\"name\":{\"72\":{}},\"parent\":{}}],[\"tojson\",{\"_index\":75,\"name\":{\"95\":{}},\"parent\":{}}],[\"touch\",{\"_index\":86,\"name\":{\"110\":{}},\"parent\":{}}],[\"type\",{\"_index\":43,\"name\":{\"54\":{}},\"parent\":{}}],[\"unique\",{\"_index\":56,\"name\":{\"67\":{},\"100\":{}},\"parent\":{}}],[\"uniqueopts\",{\"_index\":57,\"name\":{\"68\":{}},\"parent\":{}}]],\"pipeline\":[]}}"); \ No newline at end of file diff --git a/docs/agenda/6.x/assets/style.css b/docs/agenda/6.x/assets/style.css new file mode 100644 index 0000000..a16ed02 --- /dev/null +++ b/docs/agenda/6.x/assets/style.css @@ -0,0 +1,1413 @@ +@import url("./icons.css"); + +:root { + /* Light */ + --light-color-background: #fcfcfc; + --light-color-secondary-background: #fff; + --light-color-text: #222; + --light-color-text-aside: #707070; + --light-color-link: #4da6ff; + --light-color-menu-divider: #eee; + --light-color-menu-divider-focus: #000; + --light-color-menu-label: #707070; + --light-color-panel: var(--light-color-secondary-background); + --light-color-panel-divider: #eee; + --light-color-comment-tag: #707070; + --light-color-comment-tag-text: #fff; + --light-color-ts: #9600ff; + --light-color-ts-interface: #647f1b; + --light-color-ts-enum: #937210; + --light-color-ts-class: #0672de; + --light-color-ts-private: #707070; + --light-color-toolbar: #fff; + --light-color-toolbar-text: #333; + --light-icon-filter: invert(0); + --light-external-icon: url("data:image/svg+xml;utf8,"); + + /* Dark */ + --dark-color-background: #36393f; + --dark-color-secondary-background: #2f3136; + --dark-color-text: #ffffff; + --dark-color-text-aside: #e6e4e4; + --dark-color-link: #00aff4; + --dark-color-menu-divider: #eee; + --dark-color-menu-divider-focus: #000; + --dark-color-menu-label: #707070; + --dark-color-panel: var(--dark-color-secondary-background); + --dark-color-panel-divider: #818181; + --dark-color-comment-tag: #dcddde; + --dark-color-comment-tag-text: #2f3136; + --dark-color-ts: #c97dff; + --dark-color-ts-interface: #9cbe3c; + --dark-color-ts-enum: #d6ab29; + --dark-color-ts-class: #3695f3; + --dark-color-ts-private: #e2e2e2; + --dark-color-toolbar: #34373c; + --dark-color-toolbar-text: #ffffff; + --dark-icon-filter: invert(1); + --dark-external-icon: url("data:image/svg+xml;utf8,"); +} + +@media (prefers-color-scheme: light) { + :root { + --color-background: var(--light-color-background); + --color-secondary-background: var(--light-color-secondary-background); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-link: var(--light-color-link); + --color-menu-divider: var(--light-color-menu-divider); + --color-menu-divider-focus: var(--light-color-menu-divider-focus); + --color-menu-label: var(--light-color-menu-label); + --color-panel: var(--light-color-panel); + --color-panel-divider: var(--light-color-panel-divider); + --color-comment-tag: var(--light-color-comment-tag); + --color-comment-tag-text: var(--light-color-comment-tag-text); + --color-ts: var(--light-color-ts); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-class: var(--light-color-ts-class); + --color-ts-private: var(--light-color-ts-private); + --color-toolbar: var(--light-color-toolbar); + --color-toolbar-text: var(--light-color-toolbar-text); + --icon-filter: var(--light-icon-filter); + --external-icon: var(--light-external-icon); + } +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--dark-color-background); + --color-secondary-background: var(--dark-color-secondary-background); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-link: var(--dark-color-link); + --color-menu-divider: var(--dark-color-menu-divider); + --color-menu-divider-focus: var(--dark-color-menu-divider-focus); + --color-menu-label: var(--dark-color-menu-label); + --color-panel: var(--dark-color-panel); + --color-panel-divider: var(--dark-color-panel-divider); + --color-comment-tag: var(--dark-color-comment-tag); + --color-comment-tag-text: var(--dark-color-comment-tag-text); + --color-ts: var(--dark-color-ts); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-private: var(--dark-color-ts-private); + --color-toolbar: var(--dark-color-toolbar); + --color-toolbar-text: var(--dark-color-toolbar-text); + --icon-filter: var(--dark-icon-filter); + --external-icon: var(--dark-external-icon); + } +} + +body { + margin: 0; +} + +body.light { + --color-background: var(--light-color-background); + --color-secondary-background: var(--light-color-secondary-background); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-link: var(--light-color-link); + --color-menu-divider: var(--light-color-menu-divider); + --color-menu-divider-focus: var(--light-color-menu-divider-focus); + --color-menu-label: var(--light-color-menu-label); + --color-panel: var(--light-color-panel); + --color-panel-divider: var(--light-color-panel-divider); + --color-comment-tag: var(--light-color-comment-tag); + --color-comment-tag-text: var(--light-color-comment-tag-text); + --color-ts: var(--light-color-ts); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-class: var(--light-color-ts-class); + --color-ts-private: var(--light-color-ts-private); + --color-toolbar: var(--light-color-toolbar); + --color-toolbar-text: var(--light-color-toolbar-text); + --icon-filter: var(--light-icon-filter); + --external-icon: var(--light-external-icon); +} + +body.dark { + --color-background: var(--dark-color-background); + --color-secondary-background: var(--dark-color-secondary-background); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-link: var(--dark-color-link); + --color-menu-divider: var(--dark-color-menu-divider); + --color-menu-divider-focus: var(--dark-color-menu-divider-focus); + --color-menu-label: var(--dark-color-menu-label); + --color-panel: var(--dark-color-panel); + --color-panel-divider: var(--dark-color-panel-divider); + --color-comment-tag: var(--dark-color-comment-tag); + --color-comment-tag-text: var(--dark-color-comment-tag-text); + --color-ts: var(--dark-color-ts); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-private: var(--dark-color-ts-private); + --color-toolbar: var(--dark-color-toolbar); + --color-toolbar-text: var(--dark-color-toolbar-text); + --icon-filter: var(--dark-icon-filter); + --external-icon: var(--dark-external-icon); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + line-height: 1.2; +} + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +h2 { + font-size: 1.5em; + margin: 0.83em 0; +} + +h3 { + font-size: 1.17em; + margin: 1em 0; +} + +h4, +.tsd-index-panel h3 { + font-size: 1em; + margin: 1.33em 0; +} + +h5 { + font-size: 0.83em; + margin: 1.67em 0; +} + +h6 { + font-size: 0.67em; + margin: 2.33em 0; +} + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 40px; +} +@media (max-width: 640px) { + .container { + padding: 0 20px; + } +} + +.container-main { + padding-bottom: 200px; +} + +.row { + display: flex; + position: relative; + margin: 0 -10px; +} +.row:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0; +} + +.col-4, +.col-8 { + box-sizing: border-box; + float: left; + padding: 0 10px; +} + +.col-4 { + width: 33.3333333333%; +} +.col-8 { + width: 66.6666666667%; +} + +ul.tsd-descriptions > li > :first-child, +.tsd-panel > :first-child, +.col-8 > :first-child, +.col-4 > :first-child, +ul.tsd-descriptions > li > :first-child > :first-child, +.tsd-panel > :first-child > :first-child, +.col-8 > :first-child > :first-child, +.col-4 > :first-child > :first-child, +ul.tsd-descriptions > li > :first-child > :first-child > :first-child, +.tsd-panel > :first-child > :first-child > :first-child, +.col-8 > :first-child > :first-child > :first-child, +.col-4 > :first-child > :first-child > :first-child { + margin-top: 0; +} +ul.tsd-descriptions > li > :last-child, +.tsd-panel > :last-child, +.col-8 > :last-child, +.col-4 > :last-child, +ul.tsd-descriptions > li > :last-child > :last-child, +.tsd-panel > :last-child > :last-child, +.col-8 > :last-child > :last-child, +.col-4 > :last-child > :last-child, +ul.tsd-descriptions > li > :last-child > :last-child > :last-child, +.tsd-panel > :last-child > :last-child > :last-child, +.col-8 > :last-child > :last-child > :last-child, +.col-4 > :last-child > :last-child > :last-child { + margin-bottom: 0; +} + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes fade-out { + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + } +} +@keyframes fade-in-delayed { + 0% { + opacity: 0; + } + 33% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +@keyframes fade-out-delayed { + 0% { + opacity: 1; + visibility: visible; + } + 66% { + opacity: 0; + } + 100% { + opacity: 0; + } +} +@keyframes shift-to-left { + from { + transform: translate(0, 0); + } + to { + transform: translate(-25%, 0); + } +} +@keyframes unshift-to-left { + from { + transform: translate(-25%, 0); + } + to { + transform: translate(0, 0); + } +} +@keyframes pop-in-from-right { + from { + transform: translate(100%, 0); + } + to { + transform: translate(0, 0); + } +} +@keyframes pop-out-to-right { + from { + transform: translate(0, 0); + visibility: visible; + } + to { + transform: translate(100%, 0); + } +} +body { + background: var(--color-background); + font-family: "Segoe UI", sans-serif; + font-size: 16px; + color: var(--color-text); +} + +a { + color: var(--color-link); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a.external[target="_blank"] { + background-image: var(--external-icon); + background-position: top 3px right; + background-repeat: no-repeat; + padding-right: 13px; +} + +code, +pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + padding: 0.2em; + margin: 0; + font-size: 14px; +} + +pre { + padding: 10px; +} +pre code { + padding: 0; + font-size: 100%; +} + +blockquote { + margin: 1em 0; + padding-left: 1em; + border-left: 4px solid gray; +} + +.tsd-typography { + line-height: 1.333em; +} +.tsd-typography ul { + list-style: square; + padding: 0 0 0 20px; + margin: 0; +} +.tsd-typography h4, +.tsd-typography .tsd-index-panel h3, +.tsd-index-panel .tsd-typography h3, +.tsd-typography h5, +.tsd-typography h6 { + font-size: 1em; + margin: 0; +} +.tsd-typography h5, +.tsd-typography h6 { + font-weight: normal; +} +.tsd-typography p, +.tsd-typography ul, +.tsd-typography ol { + margin: 1em 0; +} + +@media (min-width: 901px) and (max-width: 1024px) { + html .col-content { + width: 72%; + } + html .col-menu { + width: 28%; + } + html .tsd-navigation { + padding-left: 10px; + } +} +@media (max-width: 900px) { + html .col-content { + float: none; + width: 100%; + } + html .col-menu { + position: fixed !important; + overflow: auto; + -webkit-overflow-scrolling: touch; + z-index: 1024; + top: 0 !important; + bottom: 0 !important; + left: auto !important; + right: 0 !important; + width: 100%; + padding: 20px 20px 0 0; + max-width: 450px; + visibility: hidden; + background-color: var(--color-panel); + transform: translate(100%, 0); + } + html .col-menu > *:last-child { + padding-bottom: 20px; + } + html .overlay { + content: ""; + display: block; + position: fixed; + z-index: 1023; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + visibility: hidden; + } + + .to-has-menu .overlay { + animation: fade-in 0.4s; + } + + .to-has-menu :is(header, footer, .col-content) { + animation: shift-to-left 0.4s; + } + + .to-has-menu .col-menu { + animation: pop-in-from-right 0.4s; + } + + .from-has-menu .overlay { + animation: fade-out 0.4s; + } + + .from-has-menu :is(header, footer, .col-content) { + animation: unshift-to-left 0.4s; + } + + .from-has-menu .col-menu { + animation: pop-out-to-right 0.4s; + } + + .has-menu body { + overflow: hidden; + } + .has-menu .overlay { + visibility: visible; + } + .has-menu :is(header, footer, .col-content) { + transform: translate(-25%, 0); + } + .has-menu .col-menu { + visibility: visible; + transform: translate(0, 0); + display: grid; + grid-template-rows: auto 1fr; + max-height: 100vh; + } + .has-menu .tsd-navigation { + max-height: 100%; + } +} + +.tsd-page-title { + padding: 70px 0 20px 0; + margin: 0 0 40px 0; + background: var(--color-panel); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.35); +} +.tsd-page-title h1 { + margin: 0; +} + +.tsd-breadcrumb { + margin: 0; + padding: 0; + color: var(--color-text-aside); +} +.tsd-breadcrumb a { + color: var(--color-text-aside); + text-decoration: none; +} +.tsd-breadcrumb a:hover { + text-decoration: underline; +} +.tsd-breadcrumb li { + display: inline; +} +.tsd-breadcrumb li:after { + content: " / "; +} + +dl.tsd-comment-tags { + overflow: hidden; +} +dl.tsd-comment-tags dt { + float: left; + padding: 1px 5px; + margin: 0 10px 0 0; + border-radius: 4px; + border: 1px solid var(--color-comment-tag); + color: var(--color-comment-tag); + font-size: 0.8em; + font-weight: normal; +} +dl.tsd-comment-tags dd { + margin: 0 0 10px 0; +} +dl.tsd-comment-tags dd:before, +dl.tsd-comment-tags dd:after { + display: table; + content: " "; +} +dl.tsd-comment-tags dd pre, +dl.tsd-comment-tags dd:after { + clear: both; +} +dl.tsd-comment-tags p { + margin: 0; +} + +.tsd-panel.tsd-comment .lead { + font-size: 1.1em; + line-height: 1.333em; + margin-bottom: 2em; +} +.tsd-panel.tsd-comment .lead:last-child { + margin-bottom: 0; +} + +.toggle-protected .tsd-is-private { + display: none; +} + +.toggle-public .tsd-is-private, +.toggle-public .tsd-is-protected, +.toggle-public .tsd-is-private-protected { + display: none; +} + +.toggle-inherited .tsd-is-inherited { + display: none; +} + +.toggle-externals .tsd-is-external { + display: none; +} + +#tsd-filter { + position: relative; + display: inline-block; + height: 40px; + vertical-align: bottom; +} +.no-filter #tsd-filter { + display: none; +} +#tsd-filter .tsd-filter-group { + display: inline-block; + height: 40px; + vertical-align: bottom; + white-space: nowrap; +} +#tsd-filter input { + display: none; +} +@media (max-width: 900px) { + #tsd-filter .tsd-filter-group { + display: block; + position: absolute; + top: 40px; + right: 20px; + height: auto; + background-color: var(--color-panel); + visibility: hidden; + transform: translate(50%, 0); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + } + .has-options #tsd-filter .tsd-filter-group { + visibility: visible; + } + .to-has-options #tsd-filter .tsd-filter-group { + animation: fade-in 0.2s; + } + .from-has-options #tsd-filter .tsd-filter-group { + animation: fade-out 0.2s; + } + #tsd-filter label, + #tsd-filter .tsd-select { + display: block; + padding-right: 20px; + } +} + +footer { + border-top: 1px solid var(--color-panel-divider); + background-color: var(--color-panel); +} +footer:after { + content: ""; + display: table; +} +footer.with-border-bottom { + border-bottom: 1px solid var(--color-panel-divider); +} +footer .tsd-legend-group { + font-size: 0; +} +footer .tsd-legend { + display: inline-block; + width: 25%; + padding: 0; + font-size: 16px; + list-style: none; + line-height: 1.333em; + vertical-align: top; +} +@media (max-width: 900px) { + footer .tsd-legend { + width: 50%; + } +} + +.tsd-hierarchy { + list-style: square; + padding: 0 0 0 20px; + margin: 0; +} +.tsd-hierarchy .target { + font-weight: bold; +} + +.tsd-index-panel .tsd-index-content { + margin-bottom: -30px !important; +} +.tsd-index-panel .tsd-index-section { + margin-bottom: 30px !important; +} +.tsd-index-panel h3 { + margin: 0 -20px 10px -20px; + padding: 0 20px 10px 20px; + border-bottom: 1px solid var(--color-panel-divider); +} +.tsd-index-panel ul.tsd-index-list { + -webkit-column-count: 3; + -moz-column-count: 3; + -ms-column-count: 3; + -o-column-count: 3; + column-count: 3; + -webkit-column-gap: 20px; + -moz-column-gap: 20px; + -ms-column-gap: 20px; + -o-column-gap: 20px; + column-gap: 20px; + padding: 0; + list-style: none; + line-height: 1.333em; +} +@media (max-width: 900px) { + .tsd-index-panel ul.tsd-index-list { + -webkit-column-count: 1; + -moz-column-count: 1; + -ms-column-count: 1; + -o-column-count: 1; + column-count: 1; + } +} +@media (min-width: 901px) and (max-width: 1024px) { + .tsd-index-panel ul.tsd-index-list { + -webkit-column-count: 2; + -moz-column-count: 2; + -ms-column-count: 2; + -o-column-count: 2; + column-count: 2; + } +} +.tsd-index-panel ul.tsd-index-list li { + -webkit-page-break-inside: avoid; + -moz-page-break-inside: avoid; + -ms-page-break-inside: avoid; + -o-page-break-inside: avoid; + page-break-inside: avoid; +} +.tsd-index-panel a, +.tsd-index-panel .tsd-parent-kind-module a { + color: var(--color-ts); +} +.tsd-index-panel .tsd-parent-kind-interface a { + color: var(--color-ts-interface); +} +.tsd-index-panel .tsd-parent-kind-enum a { + color: var(--color-ts-enum); +} +.tsd-index-panel .tsd-parent-kind-class a { + color: var(--color-ts-class); +} +.tsd-index-panel .tsd-kind-module a { + color: var(--color-ts); +} +.tsd-index-panel .tsd-kind-interface a { + color: var(--color-ts-interface); +} +.tsd-index-panel .tsd-kind-enum a { + color: var(--color-ts-enum); +} +.tsd-index-panel .tsd-kind-class a { + color: var(--color-ts-class); +} +.tsd-index-panel .tsd-is-private a { + color: var(--color-ts-private); +} + +.tsd-flag { + display: inline-block; + padding: 1px 5px; + border-radius: 4px; + color: var(--color-comment-tag-text); + background-color: var(--color-comment-tag); + text-indent: 0; + font-size: 14px; + font-weight: normal; +} + +.tsd-anchor { + position: absolute; + top: -100px; +} + +.tsd-member { + position: relative; +} +.tsd-member .tsd-anchor + h3 { + margin-top: 0; + margin-bottom: 0; + border-bottom: none; +} +.tsd-member [data-tsd-kind] { + color: var(--color-ts); +} +.tsd-member [data-tsd-kind="Interface"] { + color: var(--color-ts-interface); +} +.tsd-member [data-tsd-kind="Enum"] { + color: var(--color-ts-enum); +} +.tsd-member [data-tsd-kind="Class"] { + color: var(--color-ts-class); +} +.tsd-member [data-tsd-kind="Private"] { + color: var(--color-ts-private); +} + +.tsd-navigation { + margin: 0 0 0 40px; +} +.tsd-navigation a { + display: block; + padding-top: 2px; + padding-bottom: 2px; + border-left: 2px solid transparent; + color: var(--color-text); + text-decoration: none; + transition: border-left-color 0.1s; +} +.tsd-navigation a:hover { + text-decoration: underline; +} +.tsd-navigation ul { + margin: 0; + padding: 0; + list-style: none; +} +.tsd-navigation li { + padding: 0; +} + +.tsd-navigation.primary { + padding-bottom: 40px; +} +.tsd-navigation.primary a { + display: block; + padding-top: 6px; + padding-bottom: 6px; +} +.tsd-navigation.primary ul li a { + padding-left: 5px; +} +.tsd-navigation.primary ul li li a { + padding-left: 25px; +} +.tsd-navigation.primary ul li li li a { + padding-left: 45px; +} +.tsd-navigation.primary ul li li li li a { + padding-left: 65px; +} +.tsd-navigation.primary ul li li li li li a { + padding-left: 85px; +} +.tsd-navigation.primary ul li li li li li li a { + padding-left: 105px; +} +.tsd-navigation.primary > ul { + border-bottom: 1px solid var(--color-panel-divider); +} +.tsd-navigation.primary li { + border-top: 1px solid var(--color-panel-divider); +} +.tsd-navigation.primary li.current > a { + font-weight: bold; +} +.tsd-navigation.primary li.label span { + display: block; + padding: 20px 0 6px 5px; + color: var(--color-menu-label); +} +.tsd-navigation.primary li.globals + li > span, +.tsd-navigation.primary li.globals + li > a { + padding-top: 20px; +} + +.tsd-navigation.secondary { + max-height: calc(100vh - 1rem - 40px); + overflow: auto; + position: sticky; + top: calc(0.5rem + 40px); + transition: 0.3s; +} +.tsd-navigation.secondary.tsd-navigation--toolbar-hide { + max-height: calc(100vh - 1rem); + top: 0.5rem; +} +.tsd-navigation.secondary ul { + transition: opacity 0.2s; +} +.tsd-navigation.secondary ul li a { + padding-left: 25px; +} +.tsd-navigation.secondary ul li li a { + padding-left: 45px; +} +.tsd-navigation.secondary ul li li li a { + padding-left: 65px; +} +.tsd-navigation.secondary ul li li li li a { + padding-left: 85px; +} +.tsd-navigation.secondary ul li li li li li a { + padding-left: 105px; +} +.tsd-navigation.secondary ul li li li li li li a { + padding-left: 125px; +} +.tsd-navigation.secondary ul.current a { + border-left-color: var(--color-panel-divider); +} +.tsd-navigation.secondary li.focus > a, +.tsd-navigation.secondary ul.current li.focus > a { + border-left-color: var(--color-menu-divider-focus); +} +.tsd-navigation.secondary li.current { + margin-top: 20px; + margin-bottom: 20px; + border-left-color: var(--color-panel-divider); +} +.tsd-navigation.secondary li.current > a { + font-weight: bold; +} + +@media (min-width: 901px) { + .menu-sticky-wrap { + position: static; + } +} + +.tsd-panel { + margin: 20px 0; + padding: 20px; + background-color: var(--color-panel); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); +} +.tsd-panel:empty { + display: none; +} +.tsd-panel > h1, +.tsd-panel > h2, +.tsd-panel > h3 { + margin: 1.5em -20px 10px -20px; + padding: 0 20px 10px 20px; + border-bottom: 1px solid var(--color-panel-divider); +} +.tsd-panel > h1.tsd-before-signature, +.tsd-panel > h2.tsd-before-signature, +.tsd-panel > h3.tsd-before-signature { + margin-bottom: 0; + border-bottom: 0; +} +.tsd-panel table { + display: block; + width: 100%; + overflow: auto; + margin-top: 10px; + word-break: normal; + word-break: keep-all; + border-collapse: collapse; +} +.tsd-panel table th { + font-weight: bold; +} +.tsd-panel table th, +.tsd-panel table td { + padding: 6px 13px; + border: 1px solid var(--color-panel-divider); +} +.tsd-panel table tr { + background: var(--color-background); +} +.tsd-panel table tr:nth-child(even) { + background: var(--color-secondary-background); +} + +.tsd-panel-group { + margin: 60px 0; +} +.tsd-panel-group > h1, +.tsd-panel-group > h2, +.tsd-panel-group > h3 { + padding-left: 20px; + padding-right: 20px; +} + +#tsd-search { + transition: background-color 0.2s; +} +#tsd-search .title { + position: relative; + z-index: 2; +} +#tsd-search .field { + position: absolute; + left: 0; + top: 0; + right: 40px; + height: 40px; +} +#tsd-search .field input { + box-sizing: border-box; + position: relative; + top: -50px; + z-index: 1; + width: 100%; + padding: 0 10px; + opacity: 0; + outline: 0; + border: 0; + background: transparent; + color: var(--color-text); +} +#tsd-search .field label { + position: absolute; + overflow: hidden; + right: -40px; +} +#tsd-search .field input, +#tsd-search .title { + transition: opacity 0.2s; +} +#tsd-search .results { + position: absolute; + visibility: hidden; + top: 40px; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); +} +#tsd-search .results li { + padding: 0 10px; + background-color: var(--color-background); +} +#tsd-search .results li:nth-child(even) { + background-color: var(--color-panel); +} +#tsd-search .results li.state { + display: none; +} +#tsd-search .results li.current, +#tsd-search .results li:hover { + background-color: var(--color-panel-divider); +} +#tsd-search .results a { + display: block; +} +#tsd-search .results a:before { + top: 10px; +} +#tsd-search .results span.parent { + color: var(--color-text-aside); + font-weight: normal; +} +#tsd-search.has-focus { + background-color: var(--color-panel-divider); +} +#tsd-search.has-focus .field input { + top: 0; + opacity: 1; +} +#tsd-search.has-focus .title { + z-index: 0; + opacity: 0; +} +#tsd-search.has-focus .results { + visibility: visible; +} +#tsd-search.loading .results li.state.loading { + display: block; +} +#tsd-search.failure .results li.state.failure { + display: block; +} + +.tsd-signature { + margin: 0 0 1em 0; + padding: 10px; + border: 1px solid var(--color-panel-divider); + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 14px; + overflow-x: auto; +} +.tsd-signature.tsd-kind-icon { + padding-left: 30px; +} +.tsd-signature.tsd-kind-icon:before { + top: 10px; + left: 10px; +} +.tsd-panel > .tsd-signature { + margin-left: -20px; + margin-right: -20px; + border-width: 1px 0; +} +.tsd-panel > .tsd-signature.tsd-kind-icon { + padding-left: 40px; +} +.tsd-panel > .tsd-signature.tsd-kind-icon:before { + left: 20px; +} + +.tsd-signature-symbol { + color: var(--color-text-aside); + font-weight: normal; +} + +.tsd-signature-type { + font-style: italic; + font-weight: normal; +} + +.tsd-signatures { + padding: 0; + margin: 0 0 1em 0; + border: 1px solid var(--color-panel-divider); +} +.tsd-signatures .tsd-signature { + margin: 0; + border-width: 1px 0 0 0; + transition: background-color 0.1s; +} +.tsd-signatures .tsd-signature:first-child { + border-top-width: 0; +} +.tsd-signatures .tsd-signature.current { + background-color: var(--color-panel-divider); +} +.tsd-signatures.active > .tsd-signature { + cursor: pointer; +} +.tsd-panel > .tsd-signatures { + margin-left: -20px; + margin-right: -20px; + border-width: 1px 0; +} +.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { + padding-left: 40px; +} +.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon:before { + left: 20px; +} +.tsd-panel > a.anchor + .tsd-signatures { + border-top-width: 0; + margin-top: -20px; +} + +ul.tsd-descriptions { + position: relative; + overflow: hidden; + padding: 0; + list-style: none; +} +ul.tsd-descriptions.active > .tsd-description { + display: none; +} +ul.tsd-descriptions.active > .tsd-description.current { + display: block; +} +ul.tsd-descriptions.active > .tsd-description.fade-in { + animation: fade-in-delayed 0.3s; +} +ul.tsd-descriptions.active > .tsd-description.fade-out { + animation: fade-out-delayed 0.3s; + position: absolute; + display: block; + top: 0; + left: 0; + right: 0; + opacity: 0; + visibility: hidden; +} +ul.tsd-descriptions h4, +ul.tsd-descriptions .tsd-index-panel h3, +.tsd-index-panel ul.tsd-descriptions h3 { + font-size: 16px; + margin: 1em 0 0.5em 0; +} + +ul.tsd-parameters, +ul.tsd-type-parameters { + list-style: square; + margin: 0; + padding-left: 20px; +} +ul.tsd-parameters > li.tsd-parameter-signature, +ul.tsd-type-parameters > li.tsd-parameter-signature { + list-style: none; + margin-left: -20px; +} +ul.tsd-parameters h5, +ul.tsd-type-parameters h5 { + font-size: 16px; + margin: 1em 0 0.5em 0; +} +ul.tsd-parameters .tsd-comment, +ul.tsd-type-parameters .tsd-comment { + margin-top: -0.5em; +} + +.tsd-sources { + font-size: 14px; + color: var(--color-text-aside); + margin: 0 0 1em 0; +} +.tsd-sources a { + color: var(--color-text-aside); + text-decoration: underline; +} +.tsd-sources ul, +.tsd-sources p { + margin: 0 !important; +} +.tsd-sources ul { + list-style: none; + padding: 0; +} + +.tsd-page-toolbar { + position: fixed; + z-index: 1; + top: 0; + left: 0; + width: 100%; + height: 40px; + color: var(--color-toolbar-text); + background: var(--color-toolbar); + border-bottom: 1px solid var(--color-panel-divider); + transition: transform 0.3s linear; +} +.tsd-page-toolbar a { + color: var(--color-toolbar-text); + text-decoration: none; +} +.tsd-page-toolbar a.title { + font-weight: bold; +} +.tsd-page-toolbar a.title:hover { + text-decoration: underline; +} +.tsd-page-toolbar .table-wrap { + display: table; + width: 100%; + height: 40px; +} +.tsd-page-toolbar .table-cell { + display: table-cell; + position: relative; + white-space: nowrap; + line-height: 40px; +} +.tsd-page-toolbar .table-cell:first-child { + width: 100%; +} + +.tsd-page-toolbar--hide { + transform: translateY(-100%); +} + +.tsd-select .tsd-select-list li:before, +.tsd-select .tsd-select-label:before, +.tsd-widget:before { + content: ""; + display: inline-block; + width: 40px; + height: 40px; + margin: 0 -8px 0 0; + background-image: url(./widgets.png); + background-repeat: no-repeat; + text-indent: -1024px; + vertical-align: bottom; + filter: var(--icon-filter); +} +@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { + .tsd-select .tsd-select-list li:before, + .tsd-select .tsd-select-label:before, + .tsd-widget:before { + background-image: url(./widgets@2x.png); + background-size: 320px 40px; + } +} + +.tsd-widget { + display: inline-block; + overflow: hidden; + opacity: 0.8; + height: 40px; + transition: opacity 0.1s, background-color 0.2s; + vertical-align: bottom; + cursor: pointer; +} +.tsd-widget:hover { + opacity: 0.9; +} +.tsd-widget.active { + opacity: 1; + background-color: var(--color-panel-divider); +} +.tsd-widget.no-caption { + width: 40px; +} +.tsd-widget.no-caption:before { + margin: 0; +} +.tsd-widget.search:before { + background-position: 0 0; +} +.tsd-widget.menu:before { + background-position: -40px 0; +} +.tsd-widget.options:before { + background-position: -80px 0; +} +.tsd-widget.options, +.tsd-widget.menu { + display: none; +} +@media (max-width: 900px) { + .tsd-widget.options, + .tsd-widget.menu { + display: inline-block; + } +} +input[type="checkbox"] + .tsd-widget:before { + background-position: -120px 0; +} +input[type="checkbox"]:checked + .tsd-widget:before { + background-position: -160px 0; +} + +.tsd-select { + position: relative; + display: inline-block; + height: 40px; + transition: opacity 0.1s, background-color 0.2s; + vertical-align: bottom; + cursor: pointer; +} +.tsd-select .tsd-select-label { + opacity: 0.6; + transition: opacity 0.2s; +} +.tsd-select .tsd-select-label:before { + background-position: -240px 0; +} +.tsd-select.active .tsd-select-label { + opacity: 0.8; +} +.tsd-select.active .tsd-select-list { + visibility: visible; + opacity: 1; + transition-delay: 0s; +} +.tsd-select .tsd-select-list { + position: absolute; + visibility: hidden; + top: 40px; + left: 0; + margin: 0; + padding: 0; + opacity: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + transition: visibility 0s 0.2s, opacity 0.2s; +} +.tsd-select .tsd-select-list li { + padding: 0 20px 0 0; + background-color: var(--color-background); +} +.tsd-select .tsd-select-list li:before { + background-position: 40px 0; +} +.tsd-select .tsd-select-list li:nth-child(even) { + background-color: var(--color-panel); +} +.tsd-select .tsd-select-list li:hover { + background-color: var(--color-panel-divider); +} +.tsd-select .tsd-select-list li.selected:before { + background-position: -200px 0; +} +@media (max-width: 900px) { + .tsd-select .tsd-select-list { + top: 0; + left: auto; + right: 100%; + margin-right: -5px; + } + .tsd-select .tsd-select-label:before { + background-position: -280px 0; + } +} + +img { + max-width: 100%; +} + +.tsd-anchor-icon { + margin-left: 10px; + vertical-align: middle; + color: var(--color-text); +} + +.tsd-anchor-icon svg { + width: 1em; + height: 1em; + visibility: hidden; +} + +.tsd-anchor-link:hover > .tsd-anchor-icon svg { + visibility: visible; +} diff --git a/docs/agenda/6.x/assets/widgets.png b/docs/agenda/6.x/assets/widgets.png new file mode 100644 index 0000000000000000000000000000000000000000..c7380532ac1b45400620011c37c4dcb7aec27a4c GIT binary patch literal 480 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoH8@y+q^jrZML>b&o-U3d6^w6h1+IPUz|;DW zIZ;96kdsD>Qv^q=09&hp0GpEni<1IR%gvP3v%OR9*{MuRTKWHZyIbuBt)Ci`cU_&% z1T+i^Y)o{%281-<3TpPAUTzw5v;RY=>1rvxmPl96#kYc9hX!6V^nB|ad#(S+)}?8C zr_H+lT3B#So$T=?$(w3-{rbQ4R<@nsf$}$hwSO)A$8&`(j+wQf=Jwhb0`CvhR5DCf z^OgI)KQemrUFPH+UynC$Y~QHG%DbTVh-Skz{enNU)cV_hPu~{TD7TPZl>0&K>iuE| z7AYn$7)Jrb9GE&SfQW4q&G*@N|4cHI`VakFa5-C!ov&XD)J(qp$rJJ*9e z-sHv}#g*T7Cv048d1v~BEAzM5FztAse#q78WWC^BUCzQ U&wLp6h6BX&boFyt=akR{0G%$)mH+?% literal 0 HcmV?d00001 diff --git a/docs/agenda/6.x/assets/widgets@2x.png b/docs/agenda/6.x/assets/widgets@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4bbbd57272f3b28f47527d4951ad10f950b8ad43 GIT binary patch literal 855 zcmeAS@N?(olHy`uVBq!ia0y~yU}^xe12~w0Jcmn z@(X6T|9^jgLcx21{)7exgY)a>N6m2F0<`Rqr;B4q1>>88jUdw-7W`c)zLE*mq8W2H z-<&Jl_Hco5BuC5n@AbF5GD82~-e8-v=#zCyUX0F-o}8pPfAv`!GN$ff+TL<~@kgt} z62eO?_|&+>xBmM$@p|z`tIKEdpPf8%qI>4r7@jn<=eta*{3~?g(zz{Ke9zc-G^gr? z-7foa?LcS!hmbwzru}ICvbWLlW8;+l-}!^=c32!^nV`+`C*;0-*Y%l94pC;Cb3GXz zzSf%a!{gVr{Y_lVuUj+a)*Ca+!-Hu%xmP&&X-2CuANY8^i{D7Kg6qzP zXz_ps9+lN8ESH{K4`yu&b~I>N9xGlE&;2u*b?+Go!AhN?m-bxlLvtC#MzDF2kFzfHJ1W7ybqdefSqVhbOykd*Yi%EDuhs z4wF{ft^bv2+DDnKb8gj1FuvcV`M}luS>lO<^)8x>y1#R;a=-ZKwWTQQb)ioBbi;zh zD!f5V)8581to1LL7c9!l^PSC$NBPYif!_vAZhmL4)v4U)4UsrLYiH_9rmQDd?)(e5 z^pcH>qvBg*i0dus2r*mp4;zKvu=P#s-ti;2obl`NjjwoYd>e(oo#j_uyRb<7Pv^If zzZ|mGHmV)8^tbO%^>eqMw(@7(&3g{jEp-Najo7V75xI_ZHK*FA`elF{r5}E*d7+j_R literal 0 HcmV?d00001 diff --git a/docs/agenda/6.x/classes/Agenda.html b/docs/agenda/6.x/classes/Agenda.html new file mode 100644 index 0000000..239721d --- /dev/null +++ b/docs/agenda/6.x/classes/Agenda.html @@ -0,0 +1,269 @@ +Agenda | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • EventEmitter
    • Agenda

Index

Constructors

  • new Agenda(config?: ({ defaultConcurrency?: number; defaultLockLifetime?: number; defaultLockLimit?: number; lockLimit?: number; maxConcurrency?: number; name?: string; processEvery?: string | number } & IDbConfig) & ({ defaultConcurrency?: number; defaultLockLifetime?: number; defaultLockLimit?: number; lockLimit?: number; maxConcurrency?: number; name?: string; processEvery?: string | number } & IDatabaseOptions & IDbConfig) & ({ defaultConcurrency?: number; defaultLockLifetime?: number; defaultLockLimit?: number; lockLimit?: number; maxConcurrency?: number; name?: string; processEvery?: string | number } & IMongoOptions & IDbConfig), cb?: (error?: Error) => void): Agenda
  • Parameters

    • config: ({ defaultConcurrency?: number; defaultLockLifetime?: number; defaultLockLimit?: number; lockLimit?: number; maxConcurrency?: number; name?: string; processEvery?: string | number } & IDbConfig) & ({ defaultConcurrency?: number; defaultLockLifetime?: number; defaultLockLimit?: number; lockLimit?: number; maxConcurrency?: number; name?: string; processEvery?: string | number } & IDatabaseOptions & IDbConfig) & ({ defaultConcurrency?: number; defaultLockLifetime?: number; defaultLockLimit?: number; lockLimit?: number; maxConcurrency?: number; name?: string; processEvery?: string | number } & IMongoOptions & IDbConfig) = DefaultOptions
      +

      Agenda Config

      +
    • Optional cb: (error?: Error) => void
      +

      Callback after Agenda has started and connected to mongo

      +
        • (error?: Error): void
        • Parameters

          • Optional error: Error

          Returns void

    Returns Agenda

Properties

db: JobDbRepository
definitions: {} = {}

Type declaration

jobProcessor?: JobProcessor
ready: Promise<void>
captureRejectionSymbol: typeof captureRejectionSymbol
captureRejections: boolean
+

Sets or gets the default captureRejection value for all emitters.

+
defaultMaxListeners: number
errorMonitor: typeof errorMonitor
+

This symbol shall be used to install a listener for only monitoring 'error' +events. Listeners installed using this symbol are called before the regular +'error' listeners are called.

+

Installing a listener using this symbol does not change the behavior once an +'error' event is emitted, therefore the process will still crash if no +regular 'error' listener is installed.

+

Methods

  • addListener(eventName: string | symbol, listener: (...args: any[]) => void): Agenda
  • +

    Alias for emitter.on(eventName, listener).

    +
    since

    v0.1.26

    +

    Parameters

    • eventName: string | symbol
    • listener: (...args: any[]) => void
        • (...args: any[]): void
        • Parameters

          • Rest ...args: any[]

          Returns void

    Returns Agenda

  • +

    Cancels any jobs matching the passed MongoDB query, and removes them from the database.

    +

    Parameters

    Returns Promise<number>

  • create(name: string): Job<void>
  • create<DATA>(name: string, data: DATA): Job<DATA>
  • +

    Given a name and some data, create a new job

    +

    Parameters

    • name: string
      +

    Returns Job<void>

  • Type parameters

    • DATA = unknown

    Parameters

    • name: string
    • data: DATA

    Returns Job<DATA>

  • createJobs<DATA>(names: string[], createJob: (name: string) => Promise<Job<DATA>>): Promise<Job<DATA>[]>
  • +

    Internal helper method that uses createJob to create jobs for an array of names

    +

    Type parameters

    • DATA = unknown

    Parameters

    • names: string[]
      +

      Strings of jobs to schedule

      +
    • createJob: (name: string) => Promise<Job<DATA>>
        • (name: string): Promise<Job<DATA>>
        • Parameters

          • name: string

          Returns Promise<Job<DATA>>

    Returns Promise<Job<DATA>[]>

    array of jobs created

    +
  • database(address: string, collection?: string, options?: MongoClientOptions): Promise<Agenda>
  • +

    Connect to the spec'd MongoDB server and database.

    +

    Parameters

    • address: string
    • Optional collection: string
    • Optional options: MongoClientOptions

    Returns Promise<Agenda>

  • defaultConcurrency(num: number): Agenda
  • +

    Set the default concurrency for each job

    +

    Parameters

    • num: number
      +

      number of max concurrency

      +

    Returns Agenda

  • defaultLockLifetime(ms: number): Agenda
  • +

    Set the default lock time (in ms) +Default is 10 * 60 * 1000 ms (10 minutes)

    +

    Parameters

    • ms: number
      +

    Returns Agenda

  • defaultLockLimit(num: number): Agenda
  • define<DATA>(name: string, processor: (agendaJob: Job<DATA>, done: (error?: Error) => void) => void, options?: Partial<Pick<IJobDefinition<unknown>, "lockLimit" | "lockLifetime" | "concurrency">> & { priority?: JobPriority }): void
  • define<DATA>(name: string, processor: (agendaJob: Job<DATA>) => Promise<void>, options?: Partial<Pick<IJobDefinition<unknown>, "lockLimit" | "lockLifetime" | "concurrency">> & { priority?: JobPriority }): void
  • +

    Setup definition for job +Method is used by consumers of lib to setup their functions +BREAKING CHANGE in v4: options moved from 2nd to 3rd parameter!

    +

    Type parameters

    • DATA = any

    Parameters

    • name: string
    • processor: (agendaJob: Job<DATA>, done: (error?: Error) => void) => void
        • (agendaJob: Job<DATA>, done: (error?: Error) => void): void
        • Parameters

          • agendaJob: Job<DATA>
          • done: (error?: Error) => void
              • (error?: Error): void
              • Parameters

                • Optional error: Error

                Returns void

          Returns void

    • Optional options: Partial<Pick<IJobDefinition<unknown>, "lockLimit" | "lockLifetime" | "concurrency">> & { priority?: JobPriority }
      +

    Returns void

  • Type parameters

    • DATA = any

    Parameters

    • name: string
    • processor: (agendaJob: Job<DATA>) => Promise<void>
        • (agendaJob: Job<DATA>): Promise<void>
        • Parameters

          • agendaJob: Job<DATA>

          Returns Promise<void>

    • Optional options: Partial<Pick<IJobDefinition<unknown>, "lockLimit" | "lockLifetime" | "concurrency">> & { priority?: JobPriority }

    Returns void

  • emit(eventName: string | symbol, ...args: any[]): boolean
  • +

    Synchronously calls each of the listeners registered for the event namedeventName, in the order they were registered, passing the supplied arguments +to each.

    +

    Returns true if the event had listeners, false otherwise.

    +
    const EventEmitter = require('events');
    const myEmitter = new EventEmitter();

    // First listener
    myEmitter.on('event', function firstListener() {
    console.log('Helloooo! first listener');
    });
    // Second listener
    myEmitter.on('event', function secondListener(arg1, arg2) {
    console.log(`event with parameters ${arg1}, ${arg2} in second listener`);
    });
    // Third listener
    myEmitter.on('event', function thirdListener(...args) {
    const parameters = args.join(', ');
    console.log(`event with parameters ${parameters} in third listener`);
    });

    console.log(myEmitter.listeners('event'));

    myEmitter.emit('event', 1, 2, 3, 4, 5);

    // Prints:
    // [
    // [Function: firstListener],
    // [Function: secondListener],
    // [Function: thirdListener]
    // ]
    // Helloooo! first listener
    // event with parameters 1, 2 in second listener
    // event with parameters 1, 2, 3, 4, 5 in third listener +
    +
    since

    v0.1.26

    +

    Parameters

    • eventName: string | symbol
    • Rest ...args: any[]

    Returns boolean

  • eventNames(): (string | symbol)[]
  • +

    Returns an array listing the events for which the emitter has registered +listeners. The values in the array are strings or Symbols.

    +
    const EventEmitter = require('events');
    const myEE = new EventEmitter();
    myEE.on('foo', () => {});
    myEE.on('bar', () => {});

    const sym = Symbol('symbol');
    myEE.on(sym, () => {});

    console.log(myEE.eventNames());
    // Prints: [ 'foo', 'bar', Symbol(symbol) ] +
    +
    since

    v6.0.0

    +

    Returns (string | symbol)[]

  • every(interval: string | number, names: string[], data?: undefined, options?: { skipImmediate?: boolean; timezone?: string }): Promise<Job<void>[]>
  • every(interval: string | number, name: string, data?: undefined, options?: { skipImmediate?: boolean; timezone?: string }): Promise<Job<void>>
  • every<DATA>(interval: string | number, names: string[], data: DATA, options?: { skipImmediate?: boolean; timezone?: string }): Promise<Job<DATA>[]>
  • every<DATA>(interval: string | number, name: string, data: DATA, options?: { skipImmediate?: boolean; timezone?: string }): Promise<Job<DATA>>
  • +

    Creates a scheduled job with given interval and name/names of the job to run

    +

    Parameters

    • interval: string | number
    • names: string[]
    • Optional data: undefined
    • Optional options: { skipImmediate?: boolean; timezone?: string }
      +
      • Optional skipImmediate?: boolean
      • Optional timezone?: string

    Returns Promise<Job<void>[]>

  • Parameters

    • interval: string | number
    • name: string
    • Optional data: undefined
    • Optional options: { skipImmediate?: boolean; timezone?: string }
      • Optional skipImmediate?: boolean
      • Optional timezone?: string

    Returns Promise<Job<void>>

  • Type parameters

    • DATA = unknown

    Parameters

    • interval: string | number
    • names: string[]
    • data: DATA
    • Optional options: { skipImmediate?: boolean; timezone?: string }
      • Optional skipImmediate?: boolean
      • Optional timezone?: string

    Returns Promise<Job<DATA>[]>

  • Type parameters

    • DATA = unknown

    Parameters

    • interval: string | number
    • name: string
    • data: DATA
    • Optional options: { skipImmediate?: boolean; timezone?: string }
      • Optional skipImmediate?: boolean
      • Optional timezone?: string

    Returns Promise<Job<DATA>>

  • getMaxListeners(): number
  • +

    Returns the current max listener value for the EventEmitter which is either +set by emitter.setMaxListeners(n) or defaults to defaultMaxListeners.

    +
    since

    v1.0.0

    +

    Returns number

  • getRunningStats(fullDetails?: boolean): Promise<IAgendaStatus>
  • Parameters

    • fullDetails: boolean = false

    Returns Promise<IAgendaStatus>

  • isActiveJobProcessor(): boolean
  • jobs(query?: Filter<IJobParameters<unknown>>, sort?: Sort, limit?: number, skip?: number): Promise<Job<unknown>[]>
  • +

    Finds all jobs matching 'query'

    +

    Parameters

    • query: Filter<IJobParameters<unknown>> = {}
    • sort: Sort = {}
    • limit: number = 0
    • skip: number = 0
      +

    Returns Promise<Job<unknown>[]>

  • listenerCount(eventName: string | symbol): number
  • +

    Returns the number of listeners listening to the event named eventName.

    +
    since

    v3.2.0

    +

    Parameters

    • eventName: string | symbol
      +

      The name of the event being listened for

      +

    Returns number

  • listeners(eventName: string | symbol): Function[]
  • +

    Returns a copy of the array of listeners for the event named eventName.

    +
    server.on('connection', (stream) => {
    console.log('someone connected!');
    });
    console.log(util.inspect(server.listeners('connection')));
    // Prints: [ [Function] ] +
    +
    since

    v0.1.26

    +

    Parameters

    • eventName: string | symbol

    Returns Function[]

  • lockLimit(num: number): Agenda
  • +

    Set the default amount jobs that are allowed to be locked at one time (GLOBAL)

    +

    Parameters

    • num: number
      +

    Returns Agenda

  • maxConcurrency(num: number): Agenda
  • +

    Set the concurrency for jobs (globally), type does not matter

    +

    Parameters

    • num: number
      +

    Returns Agenda

  • mongo(mongo: Db, collection?: string): Promise<Agenda>
  • +

    Use existing mongo connectino to pass into agenda

    +

    Parameters

    • mongo: Db
    • Optional collection: string
      +

    Returns Promise<Agenda>

  • now<DATA>(name: string): Promise<Job<DATA>>
  • now<DATA>(name: string, data: DATA): Promise<Job<DATA>>
  • +

    Create a job for this exact moment

    +

    Type parameters

    • DATA = void

    Parameters

    • name: string
      +

    Returns Promise<Job<DATA>>

  • Type parameters

    • DATA = unknown

    Parameters

    • name: string
    • data: DATA

    Returns Promise<Job<DATA>>

  • off(eventName: string | symbol, listener: (...args: any[]) => void): Agenda
  • +

    Alias for emitter.removeListener().

    +
    since

    v10.0.0

    +

    Parameters

    • eventName: string | symbol
    • listener: (...args: any[]) => void
        • (...args: any[]): void
        • Parameters

          • Rest ...args: any[]

          Returns void

    Returns Agenda

  • once(eventName: string | symbol, listener: (...args: any[]) => void): Agenda
  • +

    Adds a one-timelistener function for the event named eventName. The +next time eventName is triggered, this listener is removed and then invoked.

    +
    server.once('connection', (stream) => {
    console.log('Ah, we have our first user!');
    }); +
    +

    Returns a reference to the EventEmitter, so that calls can be chained.

    +

    By default, event listeners are invoked in the order they are added. Theemitter.prependOnceListener() method can be used as an alternative to add the +event listener to the beginning of the listeners array.

    +
    const myEE = new EventEmitter();
    myEE.once('foo', () => console.log('a'));
    myEE.prependOnceListener('foo', () => console.log('b'));
    myEE.emit('foo');
    // Prints:
    // b
    // a +
    +
    since

    v0.3.0

    +

    Parameters

    • eventName: string | symbol
      +

      The name of the event.

      +
    • listener: (...args: any[]) => void
      +

      The callback function

      +
        • (...args: any[]): void
        • Parameters

          • Rest ...args: any[]

          Returns void

    Returns Agenda

  • prependListener(eventName: string | symbol, listener: (...args: any[]) => void): Agenda
  • +

    Adds the listener function to the beginning of the listeners array for the +event named eventName. No checks are made to see if the listener has +already been added. Multiple calls passing the same combination of eventNameand listener will result in the listener being added, and called, multiple +times.

    +
    server.prependListener('connection', (stream) => {
    console.log('someone connected!');
    }); +
    +

    Returns a reference to the EventEmitter, so that calls can be chained.

    +
    since

    v6.0.0

    +

    Parameters

    • eventName: string | symbol
      +

      The name of the event.

      +
    • listener: (...args: any[]) => void
      +

      The callback function

      +
        • (...args: any[]): void
        • Parameters

          • Rest ...args: any[]

          Returns void

    Returns Agenda

  • prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): Agenda
  • +

    Adds a one-timelistener function for the event named eventName to the_beginning_ of the listeners array. The next time eventName is triggered, this +listener is removed, and then invoked.

    +
    server.prependOnceListener('connection', (stream) => {
    console.log('Ah, we have our first user!');
    }); +
    +

    Returns a reference to the EventEmitter, so that calls can be chained.

    +
    since

    v6.0.0

    +

    Parameters

    • eventName: string | symbol
      +

      The name of the event.

      +
    • listener: (...args: any[]) => void
      +

      The callback function

      +
        • (...args: any[]): void
        • Parameters

          • Rest ...args: any[]

          Returns void

    Returns Agenda

  • processEvery(time: string | number): Agenda
  • +

    Set the time how often the job processor checks for new jobs to process

    +

    Parameters

    • time: string | number
      +

    Returns Agenda

  • purge(): Promise<number>
  • +

    Removes all jobs from queue

    +
    note:

    Only use after defining your jobs

    +

    Returns Promise<number>

  • rawListeners(eventName: string | symbol): Function[]
  • +

    Returns a copy of the array of listeners for the event named eventName, +including any wrappers (such as those created by .once()).

    +
    const emitter = new EventEmitter();
    emitter.once('log', () => console.log('log once'));

    // Returns a new Array with a function `onceWrapper` which has a property
    // `listener` which contains the original listener bound above
    const listeners = emitter.rawListeners('log');
    const logFnWrapper = listeners[0];

    // Logs "log once" to the console and does not unbind the `once` event
    logFnWrapper.listener();

    // Logs "log once" to the console and removes the listener
    logFnWrapper();

    emitter.on('log', () => console.log('log persistently'));
    // Will return a new Array with a single function bound by `.on()` above
    const newListeners = emitter.rawListeners('log');

    // Logs "log persistently" twice
    newListeners[0]();
    emitter.emit('log'); +
    +
    since

    v9.4.0

    +

    Parameters

    • eventName: string | symbol

    Returns Function[]

  • removeAllListeners(event?: string | symbol): Agenda
  • +

    Removes all listeners, or those of the specified eventName.

    +

    It is bad practice to remove listeners added elsewhere in the code, +particularly when the EventEmitter instance was created by some other +component or module (e.g. sockets or file streams).

    +

    Returns a reference to the EventEmitter, so that calls can be chained.

    +
    since

    v0.1.26

    +

    Parameters

    • Optional event: string | symbol

    Returns Agenda

  • removeListener(eventName: string | symbol, listener: (...args: any[]) => void): Agenda
  • +

    Removes the specified listener from the listener array for the event namedeventName.

    +
    const callback = (stream) => {
    console.log('someone connected!');
    };
    server.on('connection', callback);
    // ...
    server.removeListener('connection', callback); +
    +

    removeListener() will remove, at most, one instance of a listener from the +listener array. If any single listener has been added multiple times to the +listener array for the specified eventName, then removeListener() must be +called multiple times to remove each instance.

    +

    Once an event is emitted, all listeners attached to it at the +time of emitting are called in order. This implies that anyremoveListener() or removeAllListeners() calls after emitting and_before_ the last listener finishes execution will +not remove them fromemit() in progress. Subsequent events behave as expected.

    +
    const myEmitter = new MyEmitter();

    const callbackA = () => {
    console.log('A');
    myEmitter.removeListener('event', callbackB);
    };

    const callbackB = () => {
    console.log('B');
    };

    myEmitter.on('event', callbackA);

    myEmitter.on('event', callbackB);

    // callbackA removes listener callbackB but it will still be called.
    // Internal listener array at time of emit [callbackA, callbackB]
    myEmitter.emit('event');
    // Prints:
    // A
    // B

    // callbackB is now removed.
    // Internal listener array [callbackA]
    myEmitter.emit('event');
    // Prints:
    // A +
    +

    Because listeners are managed using an internal array, calling this will +change the position indices of any listener registered after the listener +being removed. This will not impact the order in which listeners are called, +but it means that any copies of the listener array as returned by +the emitter.listeners() method will need to be recreated.

    +

    When a single function has been added as a handler multiple times for a single +event (as in the example below), removeListener() will remove the most +recently added instance. In the example the once('ping')listener is removed:

    +
    const ee = new EventEmitter();

    function pong() {
    console.log('pong');
    }

    ee.on('ping', pong);
    ee.once('ping', pong);
    ee.removeListener('ping', pong);

    ee.emit('ping');
    ee.emit('ping'); +
    +

    Returns a reference to the EventEmitter, so that calls can be chained.

    +
    since

    v0.1.26

    +

    Parameters

    • eventName: string | symbol
    • listener: (...args: any[]) => void
        • (...args: any[]): void
        • Parameters

          • Rest ...args: any[]

          Returns void

    Returns Agenda

  • schedule<DATA>(when: string | Date, names: string[]): Promise<Job<DATA>[]>
  • schedule<DATA>(when: string | Date, names: string): Promise<Job<DATA>>
  • schedule<DATA>(when: string | Date, names: string[], data: DATA): Promise<Job<DATA>[]>
  • schedule<DATA>(when: string | Date, name: string, data: DATA): Promise<Job<DATA>>
  • +

    Schedule a job or jobs at a specific time

    +

    Type parameters

    • DATA = void

    Parameters

    • when: string | Date
    • names: string[]
      +

    Returns Promise<Job<DATA>[]>

  • Type parameters

    • DATA = void

    Parameters

    • when: string | Date
    • names: string

    Returns Promise<Job<DATA>>

  • Type parameters

    • DATA = unknown

    Parameters

    • when: string | Date
    • names: string[]
    • data: DATA

    Returns Promise<Job<DATA>[]>

  • Type parameters

    • DATA = unknown

    Parameters

    • when: string | Date
    • name: string
    • data: DATA

    Returns Promise<Job<DATA>>

  • setMaxListeners(n: number): Agenda
  • +

    By default EventEmitters will print a warning if more than 10 listeners are +added for a particular event. This is a useful default that helps finding +memory leaks. The emitter.setMaxListeners() method allows the limit to be +modified for this specific EventEmitter instance. The value can be set toInfinity (or 0) to indicate an unlimited number of listeners.

    +

    Returns a reference to the EventEmitter, so that calls can be chained.

    +
    since

    v0.3.5

    +

    Parameters

    • n: number

    Returns Agenda

  • +

    Set the sort query for finding next job +Default is { nextRunAt: 1, priority: -1 }

    +

    Parameters

    • query: {}
      +
      • [key: string]: SortDirection

    Returns Agenda

  • start(): Promise<void>
  • +

    Starts processing jobs using processJobs() methods, storing an interval ID +This method will only resolve if a db has been set up beforehand.

    +

    Returns Promise<void>

  • stop(): Promise<void>
  • +

    Clear the interval that processes the jobs and unlocks all currently locked jobs

    +

    Returns Promise<void>

  • getEventListeners(emitter: DOMEventTarget | EventEmitter, name: string | symbol): Function[]
  • +

    Returns a copy of the array of listeners for the event named eventName.

    +

    For EventEmitters this behaves exactly the same as calling .listeners on +the emitter.

    +

    For EventTargets this is the only way to get the event listeners for the +event target. This is useful for debugging and diagnostic purposes.

    +
    const { getEventListeners, EventEmitter } = require('events');

    {
    const ee = new EventEmitter();
    const listener = () => console.log('Events are fun');
    ee.on('foo', listener);
    getEventListeners(ee, 'foo'); // [listener]
    }
    {
    const et = new EventTarget();
    const listener = () => console.log('Events are fun');
    et.addEventListener('foo', listener);
    getEventListeners(et, 'foo'); // [listener]
    } +
    +
    since

    v15.2.0, v14.17.0

    +

    Parameters

    • emitter: DOMEventTarget | EventEmitter
    • name: string | symbol

    Returns Function[]

  • listenerCount(emitter: EventEmitter, eventName: string | symbol): number
  • +

    A class method that returns the number of listeners for the given eventNameregistered on the given emitter.

    +
    const { EventEmitter, listenerCount } = require('events');
    const myEmitter = new EventEmitter();
    myEmitter.on('event', () => {});
    myEmitter.on('event', () => {});
    console.log(listenerCount(myEmitter, 'event'));
    // Prints: 2 +
    +
    since

    v0.9.12

    +
    deprecated

    Since v3.2.0 - Use listenerCount instead.

    +

    Parameters

    • emitter: EventEmitter
      +

      The emitter to query

      +
    • eventName: string | symbol
      +

      The event name

      +

    Returns number

  • on(emitter: EventEmitter, eventName: string, options?: StaticEventEmitterOptions): AsyncIterableIterator<any>
  • +

    ```js +const { on, EventEmitter } = require('events');

    +

    (async () => { + const ee = new EventEmitter();

    +

    // Emit later on + process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); + });

    +

    for await (const event of on(ee, 'foo')) { + // The execution of this inner block is synchronous and it + // processes one event at a time (even with await). Do not use + // if concurrent execution is required. + console.log(event); // prints ['bar'] [42] + } + // Unreachable here +})();

    +

    Returns an `AsyncIterator` that iterates `eventName` events. It will throw
    if the `EventEmitter` emits `'error'`. It removes all listeners when
    exiting the loop. The `value` returned by each iteration is an array
    composed of the emitted event arguments.

    An `AbortSignal` can be used to cancel waiting on events:

    ```js
    const { on, EventEmitter } = require('events');
    const ac = new AbortController();

    (async () => {
    const ee = new EventEmitter();

    // Emit later on
    process.nextTick(() => {
    ee.emit('foo', 'bar');
    ee.emit('foo', 42);
    });

    for await (const event of on(ee, 'foo', { signal: ac.signal })) {
    // The execution of this inner block is synchronous and it
    // processes one event at a time (even with await). Do not use
    // if concurrent execution is required.
    console.log(event); // prints ['bar'] [42]
    }
    // Unreachable here
    })();

    process.nextTick(() => ac.abort()); +
    +
    since

    v13.6.0, v12.16.0

    +

    Parameters

    • emitter: EventEmitter
    • eventName: string
      +

      The name of the event being listened for

      +
    • Optional options: StaticEventEmitterOptions

    Returns AsyncIterableIterator<any>

    that iterates eventName events emitted by the emitter

    +
  • once(emitter: NodeEventTarget, eventName: string | symbol, options?: StaticEventEmitterOptions): Promise<any[]>
  • once(emitter: DOMEventTarget, eventName: string, options?: StaticEventEmitterOptions): Promise<any[]>
  • +

    Creates a Promise that is fulfilled when the EventEmitter emits the given +event or that is rejected if the EventEmitter emits 'error' while waiting. +The Promise will resolve with an array of all the arguments emitted to the +given event.

    +

    This method is intentionally generic and works with the web platform EventTarget interface, which has no special'error' event +semantics and does not listen to the 'error' event.

    +
    const { once, EventEmitter } = require('events');

    async function run() {
    const ee = new EventEmitter();

    process.nextTick(() => {
    ee.emit('myevent', 42);
    });

    const [value] = await once(ee, 'myevent');
    console.log(value);

    const err = new Error('kaboom');
    process.nextTick(() => {
    ee.emit('error', err);
    });

    try {
    await once(ee, 'myevent');
    } catch (err) {
    console.log('error happened', err);
    }
    }

    run(); +
    +

    The special handling of the 'error' event is only used when events.once()is used to wait for another event. If events.once() is used to wait for the +'error' event itself, then it is treated as any other kind of event without +special handling:

    +
    const { EventEmitter, once } = require('events');

    const ee = new EventEmitter();

    once(ee, 'error')
    .then(([err]) => console.log('ok', err.message))
    .catch((err) => console.log('error', err.message));

    ee.emit('error', new Error('boom'));

    // Prints: ok boom +
    +

    An AbortSignal can be used to cancel waiting for the event:

    +
    const { EventEmitter, once } = require('events');

    const ee = new EventEmitter();
    const ac = new AbortController();

    async function foo(emitter, event, signal) {
    try {
    await once(emitter, event, { signal });
    console.log('event emitted!');
    } catch (error) {
    if (error.name === 'AbortError') {
    console.error('Waiting for the event was canceled!');
    } else {
    console.error('There was an error', error.message);
    }
    }
    }

    foo(ee, 'foo', ac.signal);
    ac.abort(); // Abort waiting for the event
    ee.emit('foo'); // Prints: Waiting for the event was canceled! +
    +
    since

    v11.13.0, v10.16.0

    +

    Parameters

    • emitter: NodeEventTarget
    • eventName: string | symbol
    • Optional options: StaticEventEmitterOptions

    Returns Promise<any[]>

  • Parameters

    • emitter: DOMEventTarget
    • eventName: string
    • Optional options: StaticEventEmitterOptions

    Returns Promise<any[]>

  • setMaxListeners(n?: number, ...eventTargets: (DOMEventTarget | EventEmitter)[]): void
  • +

    By default EventEmitters will print a warning if more than 10 listeners are +added for a particular event. This is a useful default that helps finding +memory leaks. The EventEmitter.setMaxListeners() method allows the default limit to be +modified (if eventTargets is empty) or modify the limit specified in every EventTarget | EventEmitter passed as arguments. +The value can be set toInfinity (or 0) to indicate an unlimited number of listeners.

    +
    EventEmitter.setMaxListeners(20);
    // Equivalent to
    EventEmitter.defaultMaxListeners = 20;

    const eventTarget = new EventTarget();
    // Only way to increase limit for `EventTarget` instances
    // as these doesn't expose its own `setMaxListeners` method
    EventEmitter.setMaxListeners(20, eventTarget); +
    +
    since

    v15.3.0, v14.17.0

    +

    Parameters

    • Optional n: number
    • Rest ...eventTargets: (DOMEventTarget | EventEmitter)[]

    Returns void

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/classes/Job.html b/docs/agenda/6.x/classes/Job.html new file mode 100644 index 0000000..1473412 --- /dev/null +++ b/docs/agenda/6.x/classes/Job.html @@ -0,0 +1,53 @@ +Job | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class Job<DATA>

Type parameters

  • DATA = unknown | void

Hierarchy

  • Job

Index

Constructors

  • new Job<DATA>(agenda: Agenda, args: Partial<IJobParameters<void>> & { name: string; type: "normal" | "single" }, byJobProcessor?: boolean): Job<DATA>
  • new Job<DATA>(agenda: Agenda, args: Partial<IJobParameters<DATA>> & { data: DATA; name: string; type: "normal" | "single" }, byJobProcessor?: boolean): Job<DATA>
  • +

    creates a new job object

    +

    Type parameters

    • DATA = unknown

    Parameters

    • agenda: Agenda
    • args: Partial<IJobParameters<void>> & { name: string; type: "normal" | "single" }
    • Optional byJobProcessor: boolean
      +

    Returns Job<DATA>

  • +

    creates a new job object

    +

    Type parameters

    • DATA = unknown

    Parameters

    • agenda: Agenda
    • args: Partial<IJobParameters<DATA>> & { data: DATA; name: string; type: "normal" | "single" }
    • Optional byJobProcessor: boolean
      +

    Returns Job<DATA>

Properties

agenda: Agenda
attrs: IJobParameters<DATA>
canceled: undefined | Error
+

this flag is set to true, if a job got canceled (e.g. due to a timeout or other exception), +you can use it for long running tasks to periodically check if canceled is true, +also touch will check if and throws that the job got canceled

+
gotTimerToExecute: boolean
+

internal variable to ensure a job does not set unlimited numbers of setTimeouts if the job is not processed +immediately

+

Methods

  • computeNextRunAt(): Job<DATA>
  • disable(): Job<DATA>
  • enable(): Job<DATA>
  • fail(reason: string | Error): Job<DATA>
  • +

    Fails the job with a reason (error) specified

    +

    Parameters

    • reason: string | Error
      +

    Returns Job<DATA>

  • fetchStatus(): Promise<void>
  • isDead(): Promise<boolean>
  • isExpired(): boolean
  • isPromise(value: unknown): value is Promise<void>
  • Parameters

    • value: unknown

    Returns value is Promise<void>

  • isRunning(): Promise<boolean>
  • +

    A job is running if: +(lastRunAt exists AND lastFinishedAt does not exist) +OR +(lastRunAt exists AND lastFinishedAt exists but the lastRunAt is newer [in time] than lastFinishedAt)

    +

    Returns Promise<boolean>

    Whether or not job is running at the moment (true for running)

    +
  • priority(priority: JobPriority): Job<DATA>
  • +

    Sets priority of the job

    +

    Parameters

    • priority: JobPriority
      +

      priority of when job should be queued

      +

    Returns Job<DATA>

  • remove(): Promise<number>
  • +

    Remove the job from database

    +

    Returns Promise<number>

  • repeatAt(time: string): Job<DATA>
  • +

    Sets a job to repeat at a specific time

    +

    Parameters

    • time: string
      +

    Returns Job<DATA>

  • repeatEvery(interval: string | number, options?: { skipImmediate?: boolean; timezone?: string }): Job<DATA>
  • +

    Sets a job to repeat every X amount of time

    +

    Parameters

    • interval: string | number
    • options: { skipImmediate?: boolean; timezone?: string } = {}
      +
      • Optional skipImmediate?: boolean
      • Optional timezone?: string

    Returns Job<DATA>

  • run(): Promise<void>
  • save(): Promise<Job<unknown>>
  • schedule(time: string | Date): Job<DATA>
  • +

    Schedules a job to run at specified time

    +

    Parameters

    • time: string | Date
      +

    Returns Job<DATA>

  • touch(progress?: number): Promise<void>
  • +

    Updates "lockedAt" time so the job does not get picked up again

    +

    Parameters

    • Optional progress: number
      +

      0 to 100

      +

    Returns Promise<void>

  • unique(unique: Filter<Omit<IJobParameters<DATA>, "unique">>, opts?: { insertOnly: boolean }): Job<DATA>
  • +

    Data to ensure is unique for job to be created

    +

    Parameters

    • unique: Filter<Omit<IJobParameters<DATA>, "unique">>
    • Optional opts: { insertOnly: boolean }
      +
      • insertOnly: boolean

    Returns Job<DATA>

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/index.html b/docs/agenda/6.x/index.html new file mode 100644 index 0000000..6bddcc6 --- /dev/null +++ b/docs/agenda/6.x/index.html @@ -0,0 +1,819 @@ +@hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

@hokify/agenda

+ +

AgendaTS

+
+

(full typed version of agendaJS)

+

+ Agenda TS +

+

+ A light-weight job scheduling library for Node.js +

+ +

This was originally a fork of agenda.js, +it differs from the original version in following points:

+
    +
  • Complete rewrite in Typescript (fully typed!)
  • +
  • mongodb4 driver (supports mongodb 5.x)
  • +
  • Supports mongoDB sharding by name
  • +
  • touch() can have an optional progress parameter (0-100)
  • +
  • Bugfixes and improvements for locking & job processing (concurrency, lockLimit,..)
  • +
  • Breaking change: define() config paramter moved from 2nd position to 3rd
  • +
  • getRunningStats()
  • +
  • automatically waits for agenda to be connected before calling any database operations
  • +
  • uses a database abstraction layer behind the scene
  • +
  • does not create a database index by default, you can set ensureIndex: true when initializing Agenda +or run manually:
    db.agendaJobs.ensureIndex({
    "name" : 1,
    "nextRunAt" : 1,
    "priority" : -1,
    "lockedAt" : 1,
    "disabled" : 1
    }, "findAndLockNextJobIndex") +
    +
  • +
+ + +

Agenda offers

+
+
    +
  • Minimal overhead. Agenda aims to keep its code base small.
  • +
  • Mongo backed persistence layer.
  • +
  • Promises based API.
  • +
  • Scheduling with configurable priority, concurrency, and repeating.
  • +
  • Scheduling via cron or human readable syntax.
  • +
  • Event backed job queue that you can hook into.
  • +
  • Agendash: optional standalone web-interface.
  • +
  • Agenda-rest: optional standalone REST API.
  • +
  • inversify-agenda - Some utilities for the development of agenda workers with Inversify
  • +
+ + +

Feature Comparison

+
+

Since there are a few job queue solutions, here a table comparing them to help you use the one that +better suits your needs.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureBullBeeAgendaAgendaTS
Backendredisredismongomongo
Priorities✓✓✓
Concurrency✓✓✓✓
Delayed jobs✓✓✓
Global events✓
Rate Limiter✓
Pause/Resume✓~
Sandboxed worker✓
Repeatable jobs✓✓✓
Atomic ops✓✓~
Persistence✓✓✓✓
UI✓✓✓
REST API✓✓
Optimized forJobs / MessagesMessagesJobsJobs
+

Kudos for making the comparison chart goes to Bull maintainers.

+ + +

Installation

+
+

Install via NPM

+
npm install @hokify/agenda
+
+

You will also need a working Mongo database (v4+) to point it to.

+ + +

Example Usage

+
+
const mongoConnectionString = 'mongodb://127.0.0.1/agenda';

const agenda = new Agenda({db: {address: mongoConnectionString}});

// Or override the default collection name:
// const agenda = new Agenda({db: {address: mongoConnectionString, collection: 'jobCollectionName'}});

// or pass additional connection options:
// const agenda = new Agenda({db: {address: mongoConnectionString, collection: 'jobCollectionName', options: {ssl: true}}});

// or pass in an existing mongodb-native MongoClient instance
// const agenda = new Agenda({mongo: myMongoClient});

agenda.define('delete old users', async job => {
await User.remove({lastLogIn: {$lt: twoDaysAgo}});
});

(async function() { // IIFE to give access to async/await
await agenda.start();

await agenda.every('3 minutes', 'delete old users');

// Alternatively, you could also do:
await agenda.every('*/3 * * * *', 'delete old users');
})(); +
+
agenda.define('send email report', async job => {
const {to} = job.attrs.data;
await emailClient.send({
to,
from: 'example@example.com',
subject: 'Email Report',
body: '...'
});
}, {priority: 'high', concurrency: 10}, );

(async function() {
await agenda.start();
await agenda.schedule('in 20 minutes', 'send email report', {to: 'admin@example.com'});
})(); +
+
(async function() {
const weeklyReport = agenda.create('send email report', {to: 'example@example.com'});
await agenda.start();
await weeklyReport.repeatEvery('1 week').save();
})(); +
+ + +

Full documentation

+
+

See also https://hokify.github.io/agenda/

+

Agenda's basic control structure is an instance of an agenda. Agenda's are +mapped to a database collection and load the jobs from within.

+ + +

Table of Contents

+
+ + + +

Configuring an agenda

+
+

All configuration methods are chainable, meaning you can do something like:

+
const agenda = new Agenda();
agenda
.database(...)
.processEvery('3 minutes')
...; +
+

Possible agenda config options:

+
{
name: string;
defaultConcurrency: number;
processEvery: number;
maxConcurrency: number;
defaultLockLimit: number;
lockLimit: number;
defaultLockLifetime: number;
ensureIndex: boolean;
sort: SortOptionObject<IJobParameters>;
db: {
collection: string;
address: string;
options: MongoClientOptions;
};
mongo: Db;
} +
+

Agenda uses Human Interval for specifying the intervals. It supports the following units:

+

seconds, minutes, hours, days,weeks, months -- assumes 30 days, years -- assumes 365 days

+

More sophisticated examples

+
agenda.processEvery('one minute');
agenda.processEvery('1.5 minutes');
agenda.processEvery('3 days and 4 hours');
agenda.processEvery('3 days, 4 hours and 36 seconds'); +
+ + +

database(url, [collectionName], [MongoClientOptions])

+
+

Specifies the database at the url specified. If no collection name is given, +agendaJobs is used.

+

By default useNewUrlParser and useUnifiedTopology is set to true,

+
agenda.database('localhost:27017/agenda-test', 'agendaJobs');
+
+

You can also specify it during instantiation.

+
const agenda = new Agenda({db: {address: 'localhost:27017/agenda-test', collection: 'agendaJobs'}});
+
+

Agenda will emit a ready event (see Agenda Events) when properly connected to the database. +It is safe to call agenda.start() without waiting for this event, as this is handled internally. +If you're using the db options, or call database, then you may still need to listen for ready before saving jobs.

+ + +

mongo(dbInstance, [collectionName])

+
+

Use an existing mongodb-native MongoClient/Db instance. This can help consolidate connections to a +database. You can instead use .database to have agenda handle connecting for you.

+

You can also specify it during instantiation:

+
const agenda = new Agenda({mongo: mongoClientInstance.db('agenda-test')});
+
+

Note that MongoClient.connect() returns a mongoClientInstance since node-mongodb-native 3.0.0, while it used to return a dbInstance that could then be directly passed to agenda.

+ + +

name(name)

+
+

Takes a string name and sets lastModifiedBy to it in the job database. +Useful for if you have multiple job processors (agendas) and want to see which +job queue last ran the job.

+
agenda.name(os.hostname + '-' + process.pid);
+
+

You can also specify it during instantiation

+
const agenda = new Agenda({name: 'test queue'});
+
+ + +

processEvery(interval)

+
+

Takes a string interval which can be either a traditional javascript number, +or a string such as 3 minutes

+

Specifies the frequency at which agenda will query the database looking for jobs +that need to be processed. Agenda internally uses setTimeout to guarantee that +jobs run at (close to ~3ms) the right time.

+

Decreasing the frequency will result in fewer database queries, but more jobs +being stored in memory.

+

Also worth noting is that if the job queue is shutdown, any jobs stored in memory +that haven't run will still be locked, meaning that you may have to wait for the +lock to expire. By default it is '5 seconds'.

+
agenda.processEvery('1 minute');
+
+

You can also specify it during instantiation

+
const agenda = new Agenda({processEvery: '30 seconds'});
+
+ + +

maxConcurrency(number)

+
+

Takes a number which specifies the max number of jobs that can be running at +any given moment. By default it is 20.

+
agenda.maxConcurrency(20);
+
+

You can also specify it during instantiation

+
const agenda = new Agenda({maxConcurrency: 20});
+
+ + +

defaultConcurrency(number)

+
+

Takes a number which specifies the default number of a specific job that can be running at +any given moment. By default it is 5.

+
agenda.defaultConcurrency(5);
+
+

You can also specify it during instantiation

+
const agenda = new Agenda({defaultConcurrency: 5});
+
+ + +

lockLimit(number)

+
+

Takes a number which specifies the max number jobs that can be locked at any given moment. By default it is 0 for no max.

+
agenda.lockLimit(0);
+
+

You can also specify it during instantiation

+
const agenda = new Agenda({lockLimit: 0});
+
+ + +

defaultLockLimit(number)

+
+

Takes a number which specifies the default number of a specific job that can be locked at any given moment. By default it is 0 for no max.

+
agenda.defaultLockLimit(0);
+
+

You can also specify it during instantiation

+
const agenda = new Agenda({defaultLockLimit: 0});
+
+ + +

defaultLockLifetime(number)

+
+

Takes a number which specifies the default lock lifetime in milliseconds. By +default it is 10 minutes. This can be overridden by specifying the +lockLifetime option to a defined job.

+

A job will unlock if it is finished (ie. the returned Promise resolves/rejects +or done is specified in the params and done() is called) before the +lockLifetime. The lock is useful if the job crashes or times out.

+
agenda.defaultLockLifetime(10000);
+
+

You can also specify it during instantiation

+
const agenda = new Agenda({defaultLockLifetime: 10000});
+
+ + +

sort(query)

+
+

Takes a query which specifies the sort query to be used for finding and locking the next job.

+

By default it is { nextRunAt: 1, priority: -1 }, which obeys a first in first out approach, with respect to priority.

+ + +

Agenda Events

+
+

An instance of an agenda will emit the following events:

+
    +
  • ready - called when Agenda mongo connection is successfully opened and indices created. + If you're passing agenda an existing connection, you shouldn't need to listen for this, as agenda.start() will not resolve until indices have been created. + If you're using the db options, or call database, then you may still need to listen for the ready event before saving jobs. agenda.start() will still wait for the connection to be opened.
  • +
  • error - called when Agenda mongo connection process has thrown an error
  • +
+
await agenda.start();
+
+ + +

Defining Job Processors

+
+

Before you can use a job, you must define its processing behavior.

+ + +

define(jobName, fn, [options])

+
+

Defines a job with the name of jobName. When a job of jobName gets run, it +will be passed to fn(job, done). To maintain asynchronous behavior, you may +either provide a Promise-returning function in fn or provide done as a +second parameter to fn. If done is specified in the function signature, you +must call done() when you are processing the job. If your function is +synchronous or returns a Promise, you may omit done from the signature.

+

options is an optional argument which can overwrite the defaults. It can take +the following:

+
    +
  • concurrency: number maximum number of that job that can be running at once (per instance of agenda)
  • +
  • lockLimit: number maximum number of that job that can be locked at once (per instance of agenda)
  • +
  • lockLifetime: number interval in ms of how long the job stays locked for (see multiple job processors for more info). +A job will automatically unlock once a returned promise resolves/rejects (or if done is specified in the signature and done() is called).
  • +
  • priority: (lowest|low|normal|high|highest|number) specifies the priority +of the job. Higher priority jobs will run first. See the priority mapping +below
  • +
+

Priority mapping:

+
{
highest: 20,
high: 10,
normal: 0,
low: -10,
lowest: -20
} +
+

Async Job:

+
agenda.define('some long running job', async job => {
const data = await doSomelengthyTask();
await formatThatData(data);
await sendThatData(data);
}); +
+

Async Job (using done):

+
agenda.define('some long running job', (job, done) => {
doSomelengthyTask(data => {
formatThatData(data);
sendThatData(data);
done();
});
}); +
+

Sync Job:

+
agenda.define('say hello', job => {
console.log('Hello!');
}); +
+

define() acts like an assignment: if define(jobName, ...) is called multiple times (e.g. every time your script starts), the definition in the last call will overwrite the previous one. Thus, if you define the jobName only once in your code, it's safe for that call to execute multiple times.

+ + +

Creating Jobs

+
+ + +

every(interval, name, [data], [options])

+
+

Runs job name at the given interval. Optionally, data and options can be passed in. +Every creates a job of type single, which means that it will only create one +job in the database, even if that line is run multiple times. This lets you put +it in a file that may get run multiple times, such as webserver.js which may +reboot from time to time.

+

interval can be a human-readable format String, a cron format String, or a Number.

+

data is an optional argument that will be passed to the processing function +under job.attrs.data.

+

options is an optional argument that will be passed to job.repeatEvery. +In order to use this argument, data must also be specified.

+

Returns the job.

+
agenda.define('printAnalyticsReport', async job => {
const users = await User.doSomethingReallyIntensive();
processUserData(users);
console.log('I print a report!');
});

agenda.every('15 minutes', 'printAnalyticsReport'); +
+

Optionally, name could be array of job names, which is convenient for scheduling +different jobs for same interval.

+
agenda.every('15 minutes', ['printAnalyticsReport', 'sendNotifications', 'updateUserRecords']);
+
+

In this case, every returns array of jobs.

+ + +

schedule(when, name, [data])

+
+

Schedules a job to run name once at a given time. when can be a Date or a +String such as tomorrow at 5pm.

+

data is an optional argument that will be passed to the processing function +under job.attrs.data.

+

Returns the job.

+
agenda.schedule('tomorrow at noon', 'printAnalyticsReport', {userCount: 100});
+
+

Optionally, name could be array of job names, similar to the every method.

+
agenda.schedule('tomorrow at noon', ['printAnalyticsReport', 'sendNotifications', 'updateUserRecords']);
+
+

In this case, schedule returns array of jobs.

+ + +

now(name, [data])

+
+

Schedules a job to run name once immediately.

+

data is an optional argument that will be passed to the processing function +under job.attrs.data.

+

Returns the job.

+
agenda.now('do the hokey pokey');
+
+ + +

create(jobName, data)

+
+

Returns an instance of a jobName with data. This does NOT save the job in +the database. See below to learn how to manually work with jobs.

+
const job = agenda.create('printAnalyticsReport', {userCount: 100});
await job.save();
console.log('Job successfully saved'); +
+ + +

Managing Jobs

+
+ + +

jobs(mongodb-native query, mongodb-native sort, mongodb-native limit, mongodb-native skip)

+
+

Lets you query (then sort, limit and skip the result) all of the jobs in the agenda job's database. These are full mongodb-native find, sort, limit and skip commands. See mongodb-native's documentation for details.

+
const jobs = await agenda.jobs({name: 'printAnalyticsReport'}, {data:-1}, 3, 1);
// Work with jobs (see below) +
+ + +

cancel(mongodb-native query)

+
+

Cancels any jobs matching the passed mongodb-native query, and removes them from the database. Returns a Promise resolving to the number of cancelled jobs, or rejecting on error.

+
const numRemoved = await agenda.cancel({name: 'printAnalyticsReport'});
+
+

This functionality can also be achieved by first retrieving all the jobs from the database using agenda.jobs(), looping through the resulting array and calling job.remove() on each. It is however preferable to use agenda.cancel() for this use case, as this ensures the operation is atomic.

+ + +

purge()

+
+

Removes all jobs in the database without defined behaviors. Useful if you change a definition name and want to remove old jobs. Returns a Promise resolving to the number of removed jobs, or rejecting on error.

+

IMPORTANT: Do not run this before you finish defining all of your jobs. If you do, you will nuke your database of jobs.

+
const numRemoved = await agenda.purge();
+
+ + +

Starting the job processor

+
+

To get agenda to start processing jobs from the database you must start it. This +will schedule an interval (based on processEvery) to check for new jobs and +run them. You can also stop the queue.

+ + +

start

+
+

Starts the job queue processing, checking processEvery time to see if there +are new jobs. Must be called after processEvery, and before any job scheduling (e.g. every).

+ + +

stop

+
+

Stops the job queue processing. Unlocks currently running jobs.

+

This can be very useful for graceful shutdowns so that currently running/grabbed jobs are abandoned so that other +job queues can grab them / they are unlocked should the job queue start again. Here is an example of how to do a graceful +shutdown.

+
async function graceful() {
await agenda.stop();
process.exit(0);
}

process.on('SIGTERM', graceful);
process.on('SIGINT' , graceful); +
+ + +

Multiple job processors

+
+

Sometimes you may want to have multiple node instances / machines process from +the same queue. Agenda supports a locking mechanism to ensure that multiple +queues don't process the same job.

+

You can configure the locking mechanism by specifying lockLifetime as an +interval when defining the job.

+
agenda.define('someJob', (job, cb) => {
// Do something in 10 seconds or less...
}, {lockLifetime: 10000}); +
+

This will ensure that no other job processor (this one included) attempts to run the job again +for the next 10 seconds. If you have a particularly long running job, you will want to +specify a longer lockLifetime.

+

By default it is 10 minutes. Typically you shouldn't have a job that runs for 10 minutes, +so this is really insurance should the job queue crash before the job is unlocked.

+

When a job is finished (i.e. the returned promise resolves/rejects or done is +specified in the signature and done() is called), it will automatically unlock.

+ + +

Manually working with a job

+
+

A job instance has many instance methods. All mutating methods must be followed +with a call to await job.save() in order to persist the changes to the database.

+ + +

repeatEvery(interval, [options])

+
+

Specifies an interval on which the job should repeat. The job runs at the time of defining as well in configured intervals, that is "run now and in intervals".

+

interval can be a human-readable format String, a cron format String, or a Number.

+

options is an optional argument containing:

+

options.timezone: should be a string as accepted by moment-timezone and is considered when using an interval in the cron string format.

+

options.skipImmediate: true | false (default) Setting this true will skip the immediate run. The first run will occur only in configured interval.

+
job.repeatEvery('10 minutes');
await job.save(); +
+
job.repeatEvery('3 minutes', {
skipImmediate: true
});
await job.save(); +
+
job.repeatEvery('0 6 * * *', {
timezone: 'America/New_York'
});
await job.save(); +
+ + +

repeatAt(time)

+
+

Specifies a time when the job should repeat. Possible values

+
job.repeatAt('3:30pm');
await job.save(); +
+ + +

schedule(time)

+
+

Specifies the next time at which the job should run.

+
job.schedule('tomorrow at 6pm');
await job.save(); +
+ + +

priority(priority)

+
+

Specifies the priority weighting of the job. Can be a number or a string from +the above priority table.

+
job.priority('low');
await job.save(); +
+ + +

unique(properties, [options])

+
+

Ensure that only one instance of this job exists with the specified properties

+

options is an optional argument which can overwrite the defaults. It can take +the following:

+
    +
  • insertOnly: boolean will prevent any properties from persisting if the job already exists. Defaults to false.
  • +
+
job.unique({'data.type': 'active', 'data.userId': '123', nextRunAt: date});
await job.save(); +
+

IMPORTANT: To avoid high CPU usage by MongoDB, make sure to create an index on the used fields, like data.type and data.userId for the example above.

+ + +

fail(reason)

+
+

Sets job.attrs.failedAt to now, and sets job.attrs.failReason to reason.

+

Optionally, reason can be an error, in which case job.attrs.failReason will +be set to error.message

+
job.fail('insufficient disk space');
// or
job.fail(new Error('insufficient disk space'));
await job.save(); +
+ + +

run(callback)

+
+

Runs the given job and calls callback(err, job) upon completion. Normally +you never need to call this manually.

+
job.run((err, job) => {
console.log('I don\'t know why you would need to do this...');
}); +
+ + +

save()

+
+

Saves the job.attrs into the database. Returns a Promise resolving to a Job instance, or rejecting on error.

+
try {
await job.save();
cosole.log('Successfully saved job to collection');
} catch (e) {
console.error('Error saving job to collection');
} +
+ + +

remove()

+
+

Removes the job from the database. Returns a Promise resolving to the number of jobs removed, or rejecting on error.

+
try {
await job.remove();
console.log('Successfully removed job from collection');
} catch (e) {
console.error('Error removing job from collection');
} +
+ + +

disable()

+
+

Disables the job. Upcoming runs won't execute.

+ + +

enable()

+
+

Enables the job if it got disabled before. Upcoming runs will execute.

+ + +

touch()

+
+

Resets the lock on the job. Useful to indicate that the job hasn't timed out +when you have very long running jobs. The call returns a promise that resolves +when the job's lock has been renewed.

+
agenda.define('super long job', async job => {
await doSomeLongTask();
await job.touch();
await doAnotherLongTask();
await job.touch();
await finishOurLongTasks();
}); +
+ + +

Job Queue Events

+
+

An instance of an agenda will emit the following events:

+
    +
  • start - called just before a job starts
  • +
  • start:job name - called just before the specified job starts
  • +
+
agenda.on('start', job => {
console.log('Job %s starting', job.attrs.name);
}); +
+
    +
  • complete - called when a job finishes, regardless of if it succeeds or fails
  • +
  • complete:job name - called when a job finishes, regardless of if it succeeds or fails
  • +
+
agenda.on('complete', job => {
console.log(`Job ${job.attrs.name} finished`);
}); +
+
    +
  • success - called when a job finishes successfully
  • +
  • success:job name - called when a job finishes successfully
  • +
+
agenda.on('success:send email', job => {
console.log(`Sent Email Successfully to ${job.attrs.data.to}`);
}); +
+
    +
  • fail - called when a job throws an error
  • +
  • fail:job name - called when a job throws an error
  • +
+
agenda.on('fail:send email', (err, job) => {
console.log(`Job failed with error: ${err.message}`);
}); +
+ + +

Frequently Asked Questions

+
+ + +

What is the order in which jobs run?

+
+

Jobs are run with priority in a first in first out order (so they will be run in the order they were scheduled AND with respect to highest priority).

+

For example, if we have two jobs named "send-email" queued (both with the same priority), and the first job is queued at 3:00 PM and second job is queued at 3:05 PM with the same priority value, then the first job will run first if we start to send "send-email" jobs at 3:10 PM. However if the first job has a priority of 5 and the second job has a priority of 10, then the second will run first (priority takes precedence) at 3:10 PM.

+

The default MongoDB sort object is { nextRunAt: 1, priority: -1 } and can be changed through the option sort when configuring Agenda.

+ + +

What is the difference between lockLimit and maxConcurrency?

+
+

Agenda will lock jobs 1 by one, setting the lockedAt property in mongoDB, and creating an instance of the Job class which it caches into the _lockedJobs array. This defaults to having no limit, but can be managed using lockLimit. If all jobs will need to be run before agenda's next interval (set via agenda.processEvery), then agenda will attempt to lock all jobs.

+

Agenda will also pull jobs from _lockedJobs and into _runningJobs. These jobs are actively being worked on by user code, and this is limited by maxConcurrency (defaults to 20).

+

If you have multiple instances of agenda processing the same job definition with a fast repeat time you may find they get unevenly loaded. This is because they will compete to lock as many jobs as possible, even if they don't have enough concurrency to process them. This can be resolved by tweaking the maxConcurrency and lockLimit properties.

+ + +

Sample Project Structure?

+
+

Agenda doesn't have a preferred project structure and leaves it to the user to +choose how they would like to use it. That being said, you can check out the +example project structure below.

+ + +

Can I Donate?

+
+

Thanks! I'm flattered, but it's really not necessary. If you really want to, you can find my gittip here.

+ + +

Web Interface?

+
+

Agenda itself does not have a web interface built in but we do offer stand-alone web interface Agendash:

+

Agendash interface

+ + +

Mongo vs Redis

+
+

The decision to use Mongo instead of Redis is intentional. Redis is often used for +non-essential data (such as sessions) and without configuration doesn't +guarantee the same level of persistence as Mongo (should the server need to be +restarted/crash).

+

Agenda decides to focus on persistence without requiring special configuration +of Redis (thereby degrading the performance of the Redis server on non-critical +data, such as sessions).

+

Ultimately if enough people want a Redis driver instead of Mongo, I will write +one. (Please open an issue requesting it). For now, Agenda decided to focus on +guaranteed persistence.

+ + +

Spawning / forking processes

+
+

Ultimately Agenda can work from a single job queue across multiple machines, node processes, or forks. If you are interested in having more than one worker, Bars3s has written up a fantastic example of how one might do it:

+
const cluster = require('cluster');
const os = require('os');

const httpServer = require('./app/http-server');
const jobWorker = require('./app/job-worker');

const jobWorkers = [];
const webWorkers = [];

if (cluster.isMaster) {
const cpuCount = os.cpus().length;
// Create a worker for each CPU
for (let i = 0; i < cpuCount; i += 1) {
addJobWorker();
addWebWorker();
}

cluster.on('exit', (worker, code, signal) => {
if (jobWorkers.indexOf(worker.id) !== -1) {
console.log(`job worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`);
removeJobWorker(worker.id);
addJobWorker();
}

if (webWorkers.indexOf(worker.id) !== -1) {
console.log(`http worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`);
removeWebWorker(worker.id);
addWebWorker();
}
});
} else {
if (process.env.web) {
console.log(`start http server: ${cluster.worker.id}`);
// Initialize the http server here
httpServer.start();
}

if (process.env.job) {
console.log(`start job server: ${cluster.worker.id}`);
// Initialize the Agenda here
jobWorker.start();
}
}

function addWebWorker() {
webWorkers.push(cluster.fork({web: 1}).id);
}

function addJobWorker() {
jobWorkers.push(cluster.fork({job: 1}).id);
}

function removeWebWorker(id) {
webWorkers.splice(webWorkers.indexOf(id), 1);
}

function removeJobWorker(id) {
jobWorkers.splice(jobWorkers.indexOf(id), 1);
} +
+ + +

Recovering lost Mongo connections ("auto_reconnect")

+
+

Agenda is configured by default to automatically reconnect indefinitely, emitting an error event +when no connection is available on each process tick, allowing you to restore the Mongo +instance without having to restart the application.

+

However, if you are using an existing Mongo client +you'll need to configure the reconnectTries and reconnectInterval connection settings +manually, otherwise you'll find that Agenda will throw an error with the message "MongoDB connection is not recoverable, +application restart required" if the connection cannot be recovered within 30 seconds.

+ + +

Example Project Structure

+
+

Agenda will only process jobs that it has definitions for. This allows you to +selectively choose which jobs a given agenda will process.

+

Consider the following project structure, which allows us to share models with +the rest of our code base, and specify which jobs a worker processes, if any at +all.

+
- server.js
- worker.js
lib/
- agenda.js
controllers/
- user-controller.js
jobs/
- email.js
- video-processing.js
- image-processing.js
models/
- user-model.js
- blog-post.model.js +
+

Sample job processor (eg. jobs/email.js)

+
let email = require('some-email-lib'),
User = require('../models/user-model.js');

module.exports = function(agenda) {
agenda.define('registration email', async job => {
const user = await User.get(job.attrs.data.userId);
await email(user.email(), 'Thanks for registering', 'Thanks for registering ' + user.name());
});

agenda.define('reset password', async job => {
// Etc
});

// More email related jobs
}; +
+

lib/agenda.js

+
const Agenda = require('agenda');

const connectionOpts = {db: {address: 'localhost:27017/agenda-test', collection: 'agendaJobs'}};

const agenda = new Agenda(connectionOpts);

const jobTypes = process.env.JOB_TYPES ? process.env.JOB_TYPES.split(',') : [];

jobTypes.forEach(type => {
require('./jobs/' + type)(agenda);
});

if (jobTypes.length) {
agenda.start(); // Returns a promise, which should be handled appropriately
}

module.exports = agenda; +
+

lib/controllers/user-controller.js

+
let app = express(),
User = require('../models/user-model'),
agenda = require('../worker.js');

app.post('/users', (req, res, next) => {
const user = new User(req.body);
user.save(err => {
if (err) {
return next(err);
}
agenda.now('registration email', {userId: user.primary()});
res.send(201, user.toJson());
});
}); +
+

worker.js

+
require('./lib/agenda.js');
+
+

Now you can do the following in your project:

+
node server.js
+
+

Fire up an instance with no JOB_TYPES, giving you the ability to process jobs, +but not wasting resources processing jobs.

+
JOB_TYPES=email node server.js
+
+

Allow your http server to process email jobs.

+
JOB_TYPES=email node worker.js
+
+

Fire up an instance that processes email jobs.

+
JOB_TYPES=video-processing,image-processing node worker.js
+
+

Fire up an instance that processes video-processing/image-processing jobs. Good for a heavy hitting server.

+ + +

Debugging Issues

+
+

If you think you have encountered a bug, please feel free to report it here:

+

Submit Issue

+

Please provide us with as much details as possible such as:

+
    +
  • Agenda version
  • +
  • Environment (OSX, Linux, Windows, etc)
  • +
  • Small description of what happened
  • +
  • Any relevant stack track
  • +
  • Agenda logs (see below)
  • +
+ + +

To turn on logging, please set your DEBUG env variable like so:

+
+
    +
  • OSX: DEBUG="agenda:*" ts-node src/index.ts
  • +
  • Linux: DEBUG="agenda:*" ts-node src/index.ts
  • +
  • Windows CMD: set DEBUG=agenda:*
  • +
  • Windows PowerShell: $env:DEBUG = "agenda:*"
  • +
+

While not necessary, attaching a text file with this debug information would +be extremely useful in debugging certain issues and is encouraged.

+ + +

Known Issues

+
+ + +

"Multiple order-by items are not supported. Please specify a single order-by item."

+
+

When running Agenda on Azure cosmosDB, you might run into this issue caused by Agenda's sort query used for finding and locking the next job. To fix this, you can pass custom sort option: sort: { nextRunAt: 1 }

+ + +

Performance

+
+

It is recommended to set this index if you use agendash:

+
db.agendaJobs.ensureIndex({
"nextRunAt" : -1,
"lastRunAt" : -1,
"lastFinishedAt" : -1
}, "agendash2") +
+

If you have one job definition with thousand of instances, you can add this index to improve internal sorting query +for faster sortings

+
db.agendaJobs.ensureIndex({
"name" : 1,
"disabled" : 1,
"lockedAt" : 1
}, "findAndLockDeadJobs") +
+ + +

Acknowledgements

+
+ + + +

License

+
+

The MIT License

+

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/interfaces/IAgendaConfig.html b/docs/agenda/6.x/interfaces/IAgendaConfig.html new file mode 100644 index 0000000..fcc7a2e --- /dev/null +++ b/docs/agenda/6.x/interfaces/IAgendaConfig.html @@ -0,0 +1 @@ +IAgendaConfig | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface IAgendaConfig

Hierarchy

  • IAgendaConfig

Index

Properties

defaultConcurrency: number
defaultLockLifetime: number
defaultLockLimit: number
lockLimit: number
maxConcurrency: number
name?: string
processEvery: number

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/interfaces/IDatabaseOptions.html b/docs/agenda/6.x/interfaces/IDatabaseOptions.html new file mode 100644 index 0000000..23d698d --- /dev/null +++ b/docs/agenda/6.x/interfaces/IDatabaseOptions.html @@ -0,0 +1 @@ +IDatabaseOptions | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface IDatabaseOptions

Hierarchy

  • IDatabaseOptions

Index

Properties

Properties

db: { address: string; collection?: string; options?: MongoClientOptions }

Type declaration

  • address: string
  • Optional collection?: string
  • Optional options?: MongoClientOptions

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/interfaces/IDbConfig.html b/docs/agenda/6.x/interfaces/IDbConfig.html new file mode 100644 index 0000000..3ceb061 --- /dev/null +++ b/docs/agenda/6.x/interfaces/IDbConfig.html @@ -0,0 +1 @@ +IDbConfig | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface IDbConfig

Hierarchy

  • IDbConfig

Index

Properties

Properties

ensureIndex?: boolean
sort?: {}

Type declaration

  • [key: string]: SortDirection

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/interfaces/IJobDefinition.html b/docs/agenda/6.x/interfaces/IJobDefinition.html new file mode 100644 index 0000000..476354b --- /dev/null +++ b/docs/agenda/6.x/interfaces/IJobDefinition.html @@ -0,0 +1,9 @@ +IJobDefinition | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface IJobDefinition<DATA>

Type parameters

  • DATA = unknown

Hierarchy

  • IJobDefinition

Index

Properties

concurrency?: number
+

how many jobs of this kind can run in parallel/simultanously per Agenda instance

+
fn: DefinitionProcessor<DATA, void | ((error?: Error) => void)>
lockLifetime: number
+

lock lifetime in milliseconds

+
lockLimit: number
+

max number of locked jobs of this kind

+
priority?: number
+

Higher priority jobs will run first.

+

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/interfaces/IJobParameters.html b/docs/agenda/6.x/interfaces/IJobParameters.html new file mode 100644 index 0000000..03bf562 --- /dev/null +++ b/docs/agenda/6.x/interfaces/IJobParameters.html @@ -0,0 +1,4 @@ +IJobParameters | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface IJobParameters<DATA>

Type parameters

  • DATA = unknown | void

Hierarchy

  • IJobParameters

Index

Properties

_id?: ObjectId
data: DATA
disabled?: boolean
failCount?: number
failReason?: string
failedAt?: Date
lastFinishedAt?: Date
lastModifiedBy?: string
lastRunAt?: Date
lockedAt?: Date
name: string
nextRunAt: null | Date
priority: number
progress?: number
repeatAt?: string
repeatInterval?: string | number
repeatTimezone?: string
type: "normal" | "single"
+

normal: job is queued and will be processed (regular case when the user adds a new job) +single: job with this name is only queued once, if there is an exisitn gentry in the database, the job is just updated, but not newly inserted (this is used for .every())

+
unique?: Filter<Omit<IJobParameters<DATA>, "unique">>
uniqueOpts?: { insertOnly: boolean }

Type declaration

  • insertOnly: boolean

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/interfaces/IMongoOptions.html b/docs/agenda/6.x/interfaces/IMongoOptions.html new file mode 100644 index 0000000..9da44a7 --- /dev/null +++ b/docs/agenda/6.x/interfaces/IMongoOptions.html @@ -0,0 +1 @@ +IMongoOptions | @hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface IMongoOptions

Hierarchy

  • IMongoOptions

Index

Properties

Properties

db?: { collection?: string }

Type declaration

  • Optional collection?: string
mongo: Db

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/agenda/6.x/modules.html b/docs/agenda/6.x/modules.html new file mode 100644 index 0000000..bffabc7 --- /dev/null +++ b/docs/agenda/6.x/modules.html @@ -0,0 +1 @@ +@hokify/agenda
Options
All
  • Public
  • Public/Protected
  • All
Menu

@hokify/agenda

Index

Type aliases

DefinitionProcessor<DATA, CB>: (agendaJob: Job<DATA>, done: CB) => CB extends void ? Promise<void> : void

Type parameters

  • DATA

  • CB

Type declaration

    • (agendaJob: Job<DATA>, done: CB): CB extends void ? Promise<void> : void
    • Parameters

      • agendaJob: Job<DATA>
      • done: CB

      Returns CB extends void ? Promise<void> : void

JobWithId: Job & { attrs: IJobParameters & { _id: ObjectId } }
TJobDatefield: keyof Pick<IJobParameters, "lastRunAt" | "lastFinishedAt" | "nextRunAt" | "failedAt" | "lockedAt">

Variables

datefields: TJobDatefield[] = ...

Legend

  • Constructor
  • Property
  • Method
  • Private property
  • Private method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d1455c0 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +--- +title: Agenda TS — Lightweight job scheduling for Node.js +--- + +- [Changelog](https://github.com/hokify/agenda/blob/master/CHANGELOG.md#readme) +- [Issues](https://github.com/hokify/agenda/issues) +- [NPM](https://www.npmjs.com/package/@hokify/agenda) + +## Documentation + +- [v6.x.0](./agenda/6.x) - latest +- [v4.x.0](./agenda/4.x) +- [v4.0.1](./agenda/4.0.1) +- [v3.1.0](./agenda/3.1.0) +- [v2.2.0](./agenda/2.2.0) +- [v2.0.0](./agenda/2.0.0) +- [v1.0.3](./agenda/1.0.3) diff --git a/es.js b/es.js new file mode 100644 index 0000000..aae5903 --- /dev/null +++ b/es.js @@ -0,0 +1 @@ +module.exports = require('./dist/index') diff --git a/examples/concurrency.js b/examples/concurrency.js new file mode 100644 index 0000000..146b3ed --- /dev/null +++ b/examples/concurrency.js @@ -0,0 +1,71 @@ +/** + * @file Illustrate concurrency and locking + */ +import Agenda from 'agenda'; + +function time() { + return new Date().toTimeString().split(' ')[0]; +} + +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +const agenda = new Agenda({ + db: { + address: 'mongodb://127.0.0.1/agenda', + options: { useNewUrlParser: true }, + collection: `agendaJobs-${Math.random()}` // Start fresh every time + } +}); + +let jobRunCount = 1; +agenda.define( + 'long-running job', + { + lockLifetime: 5 * 1000, // Max amount of time the job should take + concurrency: 3 // Max number of job instances to run at the same time + }, + async(job, done) => { + const thisJob = jobRunCount++; + console.log(`#${thisJob} started`); + + // 3 job instances will be running at the same time, as specified by `concurrency` above + await sleep(30 * 1000); + // Comment the job processing statement above, and uncomment one of the blocks below + + /* + // Imagine a job that takes 8 seconds. That is longer than the lockLifetime, so + // we'll break it into smaller chunks (or set its lockLifetime to a higher value). + await sleep(4 * 1000); // 4000 < lockLifetime of 5000, so the job still has time to finish + await job.touch(); // tell Agenda the job is still running, which resets the lock timeout + await sleep(4 * 1000); // do another chunk of work that takes less than the lockLifetime + */ + + // Only one job will run at a time because 3000 < lockLifetime + // await sleep(3 * 1000); + + console.log(`#${thisJob} finished`); + done(); + } +); + +(async function() { + console.log(time(), 'Agenda started'); + agenda.processEvery('1 second'); + await agenda.start(); + await agenda.every('1 second', 'long-running job'); + + // Log job start and completion/failure + agenda.on('start', (job) => { + console.log(time(), `Job <${job.attrs.name}> starting`); + }); + agenda.on('success', (job) => { + console.log(time(), `Job <${job.attrs.name}> succeeded`); + }); + agenda.on('fail', (error, job) => { + console.log(time(), `Job <${job.attrs.name}> failed:`, error); + }); +})(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a54e5cd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6148 @@ +{ + "name": "agenda", + "version": "5.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agenda", + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "cron-parser": "^4.9.0", + "date.js": "~0.3.3", + "debug": "~4.3.4", + "human-interval": "~2.0.1", + "luxon": "^3.4.4", + "mongodb": "^6.5.0" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/chai": "^4.3.14", + "@types/debug": "^4.1.12", + "@types/human-interval": "^1.0.2", + "@types/luxon": "^3.4.2", + "@types/mocha": "^10.0.6", + "@types/node": "^20.12.6", + "@types/sinon": "^17.0.3", + "chai": "^5.1.0", + "delay": "6.0.0", + "eslint": "^9.0.0", + "mocha": "10.4.0", + "mongodb-memory-server": "^9.1.8", + "nyc": "^15.1.0", + "prettier": "^3.2.5", + "sinon": "17.0.1", + "standard-version": "^9.5.0", + "ts-node": "^10.9.2", + "typedoc": "^0.25.13", + "typescript": "^5.4.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz", + "integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.7", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.7", + "@babel/parser": "^7.17.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz", + "integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz", + "integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz", + "integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.0.0.tgz", + "integrity": "sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.12.3.tgz", + "integrity": "sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@hutson/parse-repository-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", + "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/nyc-config-typescript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", + "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "nyc": ">=15" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", + "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", + "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", + "dev": true + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/human-interval": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/human-interval/-/human-interval-1.0.2.tgz", + "integrity": "sha512-PCfDMUEldcADd7YKQBAEdhr/QV9hRpXFt+mpzUZbwYjAyFG5yCbt8D1O6+fzHNtuz/UiCaQOEVWQ30unvS9TgA==", + "dev": true + }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.6.tgz", + "integrity": "sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", + "dev": true + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/async-mutex": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", + "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "dev": true, + "optional": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", + "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001317", + "electron-to-chromium": "^1.4.84", + "escalade": "^3.1.1", + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bson": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.6.0.tgz", + "integrity": "sha512-BVINv2SgcMjL4oYbBuCQTpE3/VKOSxrOA8Cj/wQP7izSzlBGVomdm+TcUd0Pzy0ytLSSDweCKQ6X3f5veM5LQA==", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001317", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz", + "integrity": "sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chai": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.0.tgz", + "integrity": "sha512-kDZ7MZyM6Q1DhR9jy7dalKohXQ2yrlXkk59CR52aRKxJrobmlBNqnFQxX9xOX8w+4mz8SYlKJa/7D7ddltFXCw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.0.0", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.0.0.tgz", + "integrity": "sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/conventional-changelog": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", + "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^5.0.12", + "conventional-changelog-atom": "^2.0.8", + "conventional-changelog-codemirror": "^2.0.8", + "conventional-changelog-conventionalcommits": "^4.5.0", + "conventional-changelog-core": "^4.2.1", + "conventional-changelog-ember": "^2.0.9", + "conventional-changelog-eslint": "^3.0.9", + "conventional-changelog-express": "^2.0.6", + "conventional-changelog-jquery": "^3.0.11", + "conventional-changelog-jshint": "^2.0.9", + "conventional-changelog-preset-loader": "^2.3.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", + "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-atom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", + "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-codemirror": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", + "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-config-spec": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", + "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", + "dev": true + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", + "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", + "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", + "dev": true, + "dependencies": { + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^5.0.0", + "conventional-commits-parser": "^3.2.0", + "dateformat": "^3.0.0", + "get-pkg-repo": "^4.0.0", + "git-raw-commits": "^2.0.8", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^4.1.1", + "lodash": "^4.17.15", + "normalize-package-data": "^3.0.0", + "q": "^1.5.1", + "read-pkg": "^3.0.0", + "read-pkg-up": "^3.0.0", + "through2": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/conventional-changelog-ember": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", + "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-eslint": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", + "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-express": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", + "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-jquery": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", + "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-jshint": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", + "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", + "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "dev": true, + "dependencies": { + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-commits-parser": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", + "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-recommended-bump": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", + "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", + "dev": true, + "dependencies": { + "concat-stream": "^2.0.0", + "conventional-changelog-preset-loader": "^2.3.4", + "conventional-commits-filter": "^2.0.7", + "conventional-commits-parser": "^3.2.0", + "git-raw-commits": "^2.0.8", + "git-semver-tags": "^4.1.1", + "meow": "^8.0.0", + "q": "^1.5.1" + }, + "bin": { + "conventional-recommended-bump": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/date.js": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz", + "integrity": "sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==", + "dependencies": { + "debug": "~3.1.0" + } + }, + "node_modules/date.js/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/date.js/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", + "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/delay": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", + "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotgitignore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", + "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotgitignore/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.88", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.88.tgz", + "integrity": "sha512-oA7mzccefkvTNi9u7DXmT0LqvhnOiN2BhSrKerta7HeUC1cLoIwtbf2wL+Ah2ozh5KQd3/1njrGrwDBXx6d14Q==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.0.0.tgz", + "integrity": "sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^3.0.2", + "@eslint/js": "9.0.0", + "@humanwhocodes/config-array": "^0.12.3", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-pkg-repo": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", + "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", + "dev": true, + "dependencies": { + "@hutson/parse-repository-url": "^3.0.0", + "hosted-git-info": "^4.0.0", + "through2": "^2.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "get-pkg-repo": "src/cli.js" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-pkg-repo/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-pkg-repo/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/get-pkg-repo/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/get-pkg-repo/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/get-pkg-repo/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/git-raw-commits": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", + "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "dev": true, + "dependencies": { + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", + "dev": true, + "dependencies": { + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", + "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "dev": true, + "dependencies": { + "meow": "^8.0.0", + "semver": "^6.0.0" + }, + "bin": { + "git-semver-tags": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/git-semver-tags/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.2" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-interval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz", + "integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==", + "dependencies": { + "numbered": "^1.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "devOptional": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "devOptional": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "dependencies": { + "text-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "devOptional": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz", + "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mocha": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongodb-memory-server": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-9.1.8.tgz", + "integrity": "sha512-QumPTOlWWWzgW6dtV4bf+ZFdTxenHSSGS4ZT85Vbb+FzPVoOtfcFwYlSfC9IM4e2nw7xvnQZvWtILYseO6HE9Q==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "mongodb-memory-server-core": "9.1.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-9.1.8.tgz", + "integrity": "sha512-iCWwaP7De4lm1lRCUKB2ffUYr6GB0I/cj6fK0NV9dgwc9fA3xapHTTT/cPYRNx29M5gmAOSaOpUgjP7i2GZ/LQ==", + "dev": true, + "dependencies": { + "async-mutex": "^0.4.0", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.6", + "https-proxy-agent": "^7.0.2", + "mongodb": "^5.9.1", + "new-find-package-json": "^2.0.0", + "semver": "^7.5.4", + "tar-stream": "^3.0.0", + "tslib": "^2.6.2", + "yauzl": "^2.10.0" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "dev": true, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "dev": true, + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dev": true, + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/new-find-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", + "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/numbered": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz", + "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==" + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "devOptional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "devOptional": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/standard-version": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", + "integrity": "sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "conventional-changelog": "3.1.25", + "conventional-changelog-config-spec": "2.1.0", + "conventional-changelog-conventionalcommits": "4.6.3", + "conventional-recommended-bump": "6.1.0", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "dotgitignore": "^2.1.0", + "figures": "^3.1.0", + "find-up": "^5.0.0", + "git-semver-tags": "^4.0.0", + "semver": "^7.1.1", + "stringify-package": "^1.0.1", + "yargs": "^16.0.0" + }, + "bin": { + "standard-version": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/standard-version/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/standard-version/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/standard-version/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/standard-version/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/standard-version/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/standard-version/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/streamx": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/stringify-package": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", + "dev": true + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typedoc": { + "version": "0.25.13", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.13.tgz", + "integrity": "sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.7" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.3.tgz", + "integrity": "sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3586765 --- /dev/null +++ b/package.json @@ -0,0 +1,85 @@ +{ + "name": "pulse", + "version": "0.1.0", + "description": "The modern MongoDB-powered scheduling library for Node.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=14.0.0" + }, + "scripts": { + "release": "npm run build && standard-version", + "prepublishOnly": "npm run build", + "build": "tsc", + "test": "npm run mocha", + "lint": "eslint src", + "lint-fix": "eslint src --fix", + "mocha": "mocha --reporter spec -b", + "mocha-coverage": "nyc mocha --reporter spec -b", + "mocha-debug": "DEBUG=agenda:**,-agenda:internal:** mocha --reporter spec -b", + "mocha-debug-internal": "DEBUG=agenda:internal:** mocha --reporter spec -b", + "mocha-debug-all": "DEBUG=agenda:** mocha --reporter spec -b", + "docs": "typedoc --out docs/agenda/6.x src/index.ts" + }, + "config": { + "blanket": { + "pattern": "lib", + "data-cover-never": "node_modules" + } + }, + "repository": { + "type": "git", + "url": "git://github.com/Pulsecron/pulse.git" + }, + "keywords": [ + "pulse", + "job", + "jobs", + "cron", + "mongodb", + "scheduling", + "delayed", + "scheduler" + ], + "author": "code-xhyun ", + "license": "MIT", + "bugs": { + "url": "https://github.com/Pulsecron/pulse/issues" + }, + "dependencies": { + "cron-parser": "^4.9.0", + "date.js": "~0.3.3", + "debug": "~4.3.4", + "human-interval": "~2.0.1", + "luxon": "^3.4.4", + "mongodb": "^6.5.0" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/chai": "^4.3.14", + "@types/debug": "^4.1.12", + "@types/human-interval": "^1.0.2", + "@types/luxon": "^3.4.2", + "@types/mocha": "^10.0.6", + "@types/node": "^20.12.6", + "@types/sinon": "^17.0.3", + "chai": "^5.1.0", + "delay": "6.0.0", + "eslint": "^9.0.0", + "mocha": "10.4.0", + "mongodb-memory-server": "^9.1.8", + "nyc": "^15.1.0", + "prettier": "^3.2.5", + "sinon": "17.0.1", + "standard-version": "^9.5.0", + "ts-node": "^10.9.2", + "typedoc": "^0.25.13", + "typescript": "^5.4.4" + } +} diff --git a/src/Job.ts b/src/Job.ts new file mode 100644 index 0000000..8faf939 --- /dev/null +++ b/src/Job.ts @@ -0,0 +1,496 @@ +import * as date from 'date.js'; +import * as debug from 'debug'; +import { ObjectId } from 'mongodb'; +import { ChildProcess, fork } from 'child_process'; +import type { Agenda } from './index'; +import type { DefinitionProcessor } from './types/JobDefinition'; +import { IJobParameters, datefields, TJobDatefield } from './types/JobParameters'; +import { JobPriority, parsePriority } from './utils/priority'; +import { computeFromInterval, computeFromRepeatAt } from './utils/nextRunAt'; + +const log = debug('agenda:job'); + +/** + * @class + */ +export class Job { + readonly attrs: IJobParameters; + + /** this flag is set to true, if a job got canceled (e.g. due to a timeout or other exception), + * you can use it for long running tasks to periodically check if canceled is true, + * also touch will check if and throws that the job got canceled + */ + private canceled?: Error | true; + + getCanceledMessage() { + return typeof this.canceled === 'object' + ? this.canceled?.message || this.canceled + : this.canceled; + } + + private forkedChild?: ChildProcess; + + cancel(error?: Error) { + this.canceled = error || true; + if (this.forkedChild) { + try { + this.forkedChild.send('cancel'); + console.info('canceled child', this.attrs.name, this.attrs._id); + } catch (err) { + console.log('cannot send cancel to child'); + } + } + } + + /** internal variable to ensure a job does not set unlimited numbers of setTimeouts if the job is not processed + * immediately */ + gotTimerToExecute: boolean; + + /** + * creates a new job object + * @param agenda + * @param args + * @param byJobProcessor + */ + constructor( + agenda: Agenda, + args: Partial> & { + name: string; + type: 'normal' | 'single'; + }, + byJobProcessor?: boolean + ); + constructor( + agenda: Agenda, + args: Partial> & { + name: string; + type: 'normal' | 'single'; + data: DATA; + }, + byJobProcessor?: boolean + ); + constructor( + readonly agenda: Agenda, + args: Partial> & { + name: string; + type: 'normal' | 'single'; + data: DATA; + }, + private readonly byJobProcessor = false + ) { + // Set attrs to args + this.attrs = { + ...args, + // Set defaults if undefined + priority: parsePriority(args.priority), + nextRunAt: args.nextRunAt === undefined ? new Date() : args.nextRunAt, + type: args.type + }; + } + + /** + * Given a job, turn it into an JobParameters object + */ + toJson(): IJobParameters { + const result = {} as IJobParameters; + + for (const key of Object.keys(this.attrs)) { + if (Object.hasOwnProperty.call(this.attrs, key)) { + result[key] = + datefields.includes(key as TJobDatefield) && this.attrs[key] + ? new Date(this.attrs[key]) + : this.attrs[key]; + } + } + + return result; + } + + /** + * Sets a job to repeat every X amount of time + * @param interval + * @param options + */ + repeatEvery( + interval: string | number, + options: { timezone?: string; skipImmediate?: boolean } = {} + ): this { + this.attrs.repeatInterval = interval; + this.attrs.repeatTimezone = options.timezone; + if (options.skipImmediate) { + // Set the lastRunAt time to the nextRunAt so that the new nextRunAt will be computed in reference to the current value. + this.attrs.lastRunAt = this.attrs.nextRunAt || new Date(); + this.computeNextRunAt(); + this.attrs.lastRunAt = undefined; + } else { + this.computeNextRunAt(); + } + + return this; + } + + /** + * Sets a job to repeat at a specific time + * @param time + */ + repeatAt(time: string): this { + this.attrs.repeatAt = time; + return this; + } + + /** + * if set, a job is forked via node child process and runs in a seperate/own + * thread + * @param enableForkMode + */ + forkMode(enableForkMode: boolean): this { + this.attrs.fork = enableForkMode; + return this; + } + + /** + * Prevents the job from running + */ + disable(): this { + this.attrs.disabled = true; + return this; + } + + /** + * Allows job to run + */ + enable(): this { + this.attrs.disabled = false; + return this; + } + + /** + * Data to ensure is unique for job to be created + * @param unique + * @param opts + */ + unique( + unique: Required>['unique'], + opts?: IJobParameters['uniqueOpts'] + ): this { + this.attrs.unique = unique; + this.attrs.uniqueOpts = opts; + return this; + } + + /** + * Schedules a job to run at specified time + * @param time + */ + schedule(time: string | Date): this { + const d = new Date(time); + + this.attrs.nextRunAt = Number.isNaN(d.getTime()) ? date(time) : d; + + return this; + } + + /** + * Sets priority of the job + * @param priority priority of when job should be queued + */ + priority(priority: JobPriority): this { + this.attrs.priority = parsePriority(priority); + return this; + } + + /** + * Fails the job with a reason (error) specified + * + * @param reason + */ + fail(reason: Error | string): this { + this.attrs.failReason = reason instanceof Error ? reason.message : reason; + this.attrs.failCount = (this.attrs.failCount || 0) + 1; + const now = new Date(); + this.attrs.failedAt = now; + this.attrs.lastFinishedAt = now; + log( + '[%s:%s] fail() called [%d] times so far', + this.attrs.name, + this.attrs._id, + this.attrs.failCount + ); + return this; + } + + private async fetchStatus(): Promise { + const dbJob = await this.agenda.db.getJobs({ _id: this.attrs._id }); + if (!dbJob || dbJob.length === 0) { + // @todo: should we just return false instead? a finished job could have been removed from database, + // and then this would throw... + throw new Error(`job with id ${this.attrs._id} not found in database`); + } + + this.attrs.lastRunAt = dbJob[0].lastRunAt; + this.attrs.lockedAt = dbJob[0].lockedAt; + this.attrs.lastFinishedAt = dbJob[0].lastFinishedAt; + } + + /** + * A job is running if: + * (lastRunAt exists AND lastFinishedAt does not exist) + * OR + * (lastRunAt exists AND lastFinishedAt exists but the lastRunAt is newer [in time] than lastFinishedAt) + * @returns Whether or not job is running at the moment (true for running) + */ + async isRunning(): Promise { + if (!this.byJobProcessor || this.attrs.fork) { + // we have no job definition, therfore we are not the job processor, but a client call + // so we get the real state from database + await this.fetchStatus(); + } + + if (!this.attrs.lastRunAt) { + return false; + } + + if (!this.attrs.lastFinishedAt) { + return true; + } + + if ( + this.attrs.lockedAt && + this.attrs.lastRunAt.getTime() > this.attrs.lastFinishedAt.getTime() + ) { + return true; + } + + return false; + } + + /** + * Saves a job to database + */ + async save(): Promise { + if (this.agenda.forkedWorker) { + const warning = new Error('calling save() on a Job during a forkedWorker has no effect!'); + console.warn(warning.message, warning.stack); + return this as Job; + } + // ensure db connection is ready + await this.agenda.ready; + return this.agenda.db.saveJob(this as Job); + } + + /** + * Remove the job from database + */ + remove(): Promise { + return this.agenda.cancel({ _id: this.attrs._id }); + } + + async isDead(): Promise { + return this.isExpired(); + } + + async isExpired(): Promise { + if (!this.byJobProcessor || this.attrs.fork) { + // we have no job definition, therfore we are not the job processor, but a client call + // so we get the real state from database + await this.fetchStatus(); + } + + const definition = this.agenda.definitions[this.attrs.name]; + + const lockDeadline = new Date(Date.now() - definition.lockLifetime); + + // This means a job has "expired", as in it has not been "touched" within the lockoutTime + // Remove from local lock + if (this.attrs.lockedAt && this.attrs.lockedAt < lockDeadline) { + return true; + } + return false; + } + + /** + * Updates "lockedAt" time so the job does not get picked up again + * @param progress 0 to 100 + */ + async touch(progress?: number): Promise { + if (this.canceled) { + throw new Error(`job ${this.attrs.name} got canceled already: ${this.canceled}!`); + } + this.attrs.lockedAt = new Date(); + this.attrs.progress = progress; + + await this.agenda.db.saveJobState(this); + } + + private computeNextRunAt() { + try { + if (this.attrs.repeatInterval) { + this.attrs.nextRunAt = computeFromInterval(this.attrs); + log( + '[%s:%s] nextRunAt set to [%s]', + this.attrs.name, + this.attrs._id, + new Date(this.attrs.nextRunAt).toISOString() + ); + } else if (this.attrs.repeatAt) { + this.attrs.nextRunAt = computeFromRepeatAt(this.attrs); + + log( + '[%s:%s] nextRunAt set to [%s]', + this.attrs.name, + this.attrs._id, + this.attrs.nextRunAt.toISOString() + ); + } else { + this.attrs.nextRunAt = null; + } + } catch (error: any) { + this.attrs.nextRunAt = null; + this.fail(error); + } + + return this; + } + + async run(): Promise { + this.attrs.lastRunAt = new Date(); + log( + '[%s:%s] setting lastRunAt to: %s', + this.attrs.name, + this.attrs._id, + this.attrs.lastRunAt.toISOString() + ); + this.computeNextRunAt(); + await this.agenda.db.saveJobState(this); + + try { + this.agenda.emit('start', this); + this.agenda.emit(`start:${this.attrs.name}`, this); + log('[%s:%s] starting job', this.attrs.name, this.attrs._id); + + if (this.attrs.fork) { + if (!this.agenda.forkHelper) { + throw new Error('no forkHelper specified, you need to set a path to a helper script'); + } + const { forkHelper } = this.agenda; + + await new Promise((resolve, reject) => { + this.forkedChild = fork( + forkHelper.path, + [ + this.attrs.name, + this.attrs._id!.toString(), + this.agenda.definitions[this.attrs.name].filePath || '' + ], + forkHelper.options + ); + + let childError: any; + this.forkedChild.on('close', code => { + if (code) { + console.info( + 'fork parameters', + forkHelper, + this.attrs.name, + this.attrs._id, + this.agenda.definitions[this.attrs.name].filePath + ); + const error = new Error(`child process exited with code: ${code}`); + console.warn(error.message, childError || this.canceled); + reject(childError || this.canceled || error); + } else { + resolve(); + } + }); + this.forkedChild.on('message', message => { + // console.log(`Message from child.js: ${message}`, JSON.stringify(message)); + if (typeof message === 'string') { + try { + childError = JSON.parse(message); + } catch (errJson) { + childError = message; + } + } else { + childError = message; + } + }); + }); + } else { + await this.runJob(); + } + + this.attrs.lastFinishedAt = new Date(); + + this.agenda.emit('success', this); + this.agenda.emit(`success:${this.attrs.name}`, this); + log('[%s:%s] has succeeded', this.attrs.name, this.attrs._id); + } catch (error: any) { + log('[%s:%s] unknown error occurred', this.attrs.name, this.attrs._id); + + this.fail(error); + + this.agenda.emit('fail', error, this); + this.agenda.emit(`fail:${this.attrs.name}`, error, this); + log('[%s:%s] has failed [%s]', this.attrs.name, this.attrs._id, error.message); + } finally { + this.forkedChild = undefined; + this.attrs.lockedAt = undefined; + try { + await this.agenda.db.saveJobState(this); + log('[%s:%s] was saved successfully to MongoDB', this.attrs.name, this.attrs._id); + } catch (err) { + // in case this fails, we ignore it + // this can e.g. happen if the job gets removed during the execution + log('[%s:%s] was not saved to MongoDB', this.attrs.name, this.attrs._id, err); + } + + this.agenda.emit('complete', this); + this.agenda.emit(`complete:${this.attrs.name}`, this); + log( + '[%s:%s] job finished at [%s] and was unlocked', + this.attrs.name, + this.attrs._id, + this.attrs.lastFinishedAt + ); + } + } + + async runJob() { + const definition = this.agenda.definitions[this.attrs.name]; + + if (!definition) { + log('[%s:%s] has no definition, can not run', this.attrs.name, this.attrs._id); + throw new Error('Undefined job'); + } + + if (definition.fn.length === 2) { + log('[%s:%s] process function being called', this.attrs.name, this.attrs._id); + await new Promise((resolve, reject) => { + try { + const result = definition.fn(this as Job, error => { + if (error) { + reject(error); + return; + } + resolve(); + }); + + if (this.isPromise(result)) { + result.catch((error: Error) => reject(error)); + } + } catch (error) { + reject(error); + } + }); + } else { + log('[%s:%s] process function being called', this.attrs.name, this.attrs._id); + await (definition.fn as DefinitionProcessor)(this); + } + } + + private isPromise(value: unknown): value is Promise { + return !!(value && typeof (value as Promise).then === 'function'); + } +} + +export type JobWithId = Job & { attrs: IJobParameters & { _id: ObjectId } }; diff --git a/src/JobDbRepository.ts b/src/JobDbRepository.ts new file mode 100644 index 0000000..3f216c7 --- /dev/null +++ b/src/JobDbRepository.ts @@ -0,0 +1,400 @@ +import * as debug from 'debug'; +import { + Collection, + Db, + Filter, + FindOneAndUpdateOptions, + MongoClient, + MongoClientOptions, + ObjectId, + Sort, + UpdateFilter +} from 'mongodb'; +import type { Job, JobWithId } from './Job'; +import type { Agenda } from './index'; +import type { IDatabaseOptions, IDbConfig, IMongoOptions } from './types/DbOptions'; +import type { IJobParameters } from './types/JobParameters'; +import { hasMongoProtocol } from './utils/hasMongoProtocol'; + +const log = debug('agenda:db'); + +/** + * @class + */ +export class JobDbRepository { + collection: Collection; + + constructor( + private agenda: Agenda, + private connectOptions: (IDatabaseOptions | IMongoOptions) & IDbConfig + ) { + this.connectOptions.sort = this.connectOptions.sort || { nextRunAt: 1, priority: -1 }; + } + + private async createConnection(): Promise { + const { connectOptions } = this; + if (this.hasDatabaseConfig(connectOptions)) { + log('using database config', connectOptions); + return this.database(connectOptions.db.address, connectOptions.db.options); + } + + if (this.hasMongoConnection(connectOptions)) { + log('using passed in mongo connection'); + return connectOptions.mongo; + } + + throw new Error('invalid db config, or db config not found'); + } + + private hasMongoConnection(connectOptions: unknown): connectOptions is IMongoOptions { + return !!(connectOptions as IMongoOptions)?.mongo; + } + + private hasDatabaseConfig(connectOptions: unknown): connectOptions is IDatabaseOptions { + return !!(connectOptions as IDatabaseOptions)?.db?.address; + } + + async getJobById(id: string) { + return this.collection.findOne({ _id: new ObjectId(id) }); + } + + async getJobs( + query: Filter, + sort: Sort = {}, + limit = 0, + skip = 0 + ): Promise { + return this.collection.find(query).sort(sort).limit(limit).skip(skip).toArray(); + } + + async removeJobs(query: Filter): Promise { + const result = await this.collection.deleteMany(query); + return result.deletedCount || 0; + } + + async getQueueSize(): Promise { + return this.collection.countDocuments({ nextRunAt: { $lt: new Date() } }); + } + + async unlockJob(job: Job): Promise { + // only unlock jobs which are not currently processed (nextRunAT is not null) + await this.collection.updateOne( + { _id: job.attrs._id, nextRunAt: { $ne: null } }, + { $unset: { lockedAt: true } } + ); + } + + /** + * Internal method to unlock jobs so that they can be re-run + */ + async unlockJobs(jobIds: ObjectId[]): Promise { + await this.collection.updateMany( + { _id: { $in: jobIds }, nextRunAt: { $ne: null } }, + { $unset: { lockedAt: true } } + ); + } + + async lockJob(job: JobWithId): Promise { + // Query to run against collection to see if we need to lock it + const criteria: Filter & { lockedAt?: Date | null }> = { + _id: job.attrs._id, + name: job.attrs.name, + lockedAt: null, + nextRunAt: job.attrs.nextRunAt, + disabled: { $ne: true } + }; + + // Update / options for the MongoDB query + const update: UpdateFilter = { $set: { lockedAt: new Date() } }; + const options: FindOneAndUpdateOptions = { + returnDocument: 'after', + sort: this.connectOptions.sort + }; + + // Lock the job in MongoDB! + const resp = await this.collection.findOneAndUpdate( + criteria as Filter, + update, + options + ); + + return resp?.value || undefined; + } + + async getNextJobToRun( + jobName: string, + nextScanAt: Date, + lockDeadline: Date, + now: Date = new Date() + ): Promise { + /** + * Query used to find job to run + */ + const JOB_PROCESS_WHERE_QUERY: Filter & { lockedAt?: Date | null } */> = + { + name: jobName, + disabled: { $ne: true }, + $or: [ + { + lockedAt: { $eq: null as any }, + nextRunAt: { $lte: nextScanAt } + }, + { + lockedAt: { $lte: lockDeadline } + } + ] + }; + + /** + * Query used to set a job as locked + */ + const JOB_PROCESS_SET_QUERY: UpdateFilter = { $set: { lockedAt: now } }; + + /** + * Query used to affect what gets returned + */ + const JOB_RETURN_QUERY: FindOneAndUpdateOptions = { + returnDocument: 'after', + sort: this.connectOptions.sort + }; + + // Find ONE and ONLY ONE job and set the 'lockedAt' time so that job begins to be processed + const result = await this.collection.findOneAndUpdate( + JOB_PROCESS_WHERE_QUERY, + JOB_PROCESS_SET_QUERY, + JOB_RETURN_QUERY + ); + + return result.value || undefined; + } + + async connect(): Promise { + const db = await this.createConnection(); + log('successful connection to MongoDB', db.options); + + const collection = this.connectOptions.db?.collection || 'agendaJobs'; + + this.collection = db.collection(collection); + if (log.enabled) { + log( + `connected with collection: ${collection}, collection size: ${ + typeof this.collection.estimatedDocumentCount === 'function' + ? await this.collection.estimatedDocumentCount() + : '?' + }` + ); + } + + if (this.connectOptions.ensureIndex) { + log('attempting index creation'); + try { + const result = await this.collection.createIndex( + { + name: 1, + ...this.connectOptions.sort, + priority: -1, + lockedAt: 1, + nextRunAt: 1, + disabled: 1 + }, + { name: 'findAndLockNextJobIndex' } + ); + log('index succesfully created', result); + } catch (error) { + log('db index creation failed', error); + throw error; + } + } + + this.agenda.emit('ready'); + } + + private async database(url: string, options?: MongoClientOptions) { + let connectionString = url; + + if (!hasMongoProtocol(connectionString)) { + connectionString = `mongodb://${connectionString}`; + } + + const client = await MongoClient.connect(connectionString, { + ...options + }); + + return client.db(); + } + + private processDbResult( + job: Job, + res?: IJobParameters + ): Job { + log( + 'processDbResult() called with success, checking whether to process job immediately or not' + ); + + // We have a result from the above calls + if (res) { + // Grab ID and nextRunAt from MongoDB and store it as an attribute on Job + job.attrs._id = res._id; + job.attrs.nextRunAt = res.nextRunAt; + + // check if we should process the job immediately + this.agenda.emit('processJob', job); + } + + // Return the Job instance + return job; + } + + async saveJobState(job: Job): Promise { + const id = job.attrs._id; + const $set = { + lockedAt: (job.attrs.lockedAt && new Date(job.attrs.lockedAt)) || undefined, + nextRunAt: (job.attrs.nextRunAt && new Date(job.attrs.nextRunAt)) || undefined, + lastRunAt: (job.attrs.lastRunAt && new Date(job.attrs.lastRunAt)) || undefined, + progress: job.attrs.progress, + failReason: job.attrs.failReason, + failCount: job.attrs.failCount, + failedAt: job.attrs.failedAt && new Date(job.attrs.failedAt), + lastFinishedAt: (job.attrs.lastFinishedAt && new Date(job.attrs.lastFinishedAt)) || undefined + }; + + log('[job %s] save job state: \n%O', id, $set); + + const result = await this.collection.updateOne( + { _id: id, name: job.attrs.name }, + { + $set + } + ); + + if (!result.acknowledged || result.matchedCount !== 1) { + throw new Error( + `job ${id} (name: ${job.attrs.name}) cannot be updated in the database, maybe it does not exist anymore?` + ); + } + } + + /** + * Save the properties on a job to MongoDB + * @name Agenda#saveJob + * @function + * @param {Job} job job to save into MongoDB + * @returns {Promise} resolves when job is saved or errors + */ + async saveJob(job: Job): Promise> { + try { + log('attempting to save a job'); + + // Grab information needed to save job but that we don't want to persist in MongoDB + const id = job.attrs._id; + + // Store job as JSON and remove props we don't want to store from object + // _id, unique, uniqueOpts + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { _id, unique, uniqueOpts, ...props } = { + ...job.toJson(), + // Store name of agenda queue as last modifier in job data + lastModifiedBy: this.agenda.attrs.name + }; + + log('[job %s] set job props: \n%O', id, props); + + // Grab current time and set default query options for MongoDB + const now = new Date(); + const protect: Partial = {}; + let update: UpdateFilter = { $set: props }; + log('current time stored as %s', now.toISOString()); + + // If the job already had an ID, then update the properties of the job + // i.e, who last modified it, etc + if (id) { + // Update the job and process the resulting data' + log('job already has _id, calling findOneAndUpdate() using _id as query'); + const result = await this.collection.findOneAndUpdate( + { _id: id, name: props.name }, + update, + { returnDocument: 'after' } + ); + return this.processDbResult(job, result.value as IJobParameters); + } + + if (props.type === 'single') { + // Job type set to 'single' so... + log('job with type of "single" found'); + + // If the nextRunAt time is older than the current time, "protect" that property, meaning, don't change + // a scheduled job's next run time! + if (props.nextRunAt && props.nextRunAt <= now) { + log('job has a scheduled nextRunAt time, protecting that field from upsert'); + protect.nextRunAt = props.nextRunAt; + delete (props as Partial).nextRunAt; + } + + // If we have things to protect, set them in MongoDB using $setOnInsert + if (Object.keys(protect).length > 0) { + update.$setOnInsert = protect; + } + + // Try an upsert + log( + `calling findOneAndUpdate(${props.name}) with job name and type of "single" as query`, + await this.collection.findOne({ + name: props.name, + type: 'single' + }) + ); + // this call ensure a job of this name can only exists once + const result = await this.collection.findOneAndUpdate( + { + name: props.name, + type: 'single' + }, + update, + { + upsert: true, + returnDocument: 'after' + } + ); + log( + `findOneAndUpdate(${props.name}) with type "single" ${ + result.lastErrorObject?.updatedExisting + ? 'updated existing entry' + : 'inserted new entry' + }` + ); + return this.processDbResult(job, result.value as IJobParameters); + } + + if (job.attrs.unique) { + // If we want the job to be unique, then we can upsert based on the 'unique' query object that was passed in + const query: Filter, 'unique'>> = job.attrs.unique; + query.name = props.name; + if (job.attrs.uniqueOpts?.insertOnly) { + update = { $setOnInsert: props }; + } + + // Use the 'unique' query object to find an existing job or create a new one + log('calling findOneAndUpdate() with unique object as query: \n%O', query); + const result = await this.collection.findOneAndUpdate(query as IJobParameters, update, { + upsert: true, + returnDocument: 'after' + }); + return this.processDbResult(job, result.value as IJobParameters); + } + + // If all else fails, the job does not exist yet so we just insert it into MongoDB + log( + 'using default behavior, inserting new job via insertOne() with props that were set: \n%O', + props + ); + const result = await this.collection.insertOne(props); + return this.processDbResult(job, { + _id: result.insertedId, + ...props + } as IJobParameters); + } catch (error) { + log('processDbResult() received an error, job was not updated/created'); + throw error; + } + } +} diff --git a/src/JobProcessingQueue.ts b/src/JobProcessingQueue.ts new file mode 100644 index 0000000..893994b --- /dev/null +++ b/src/JobProcessingQueue.ts @@ -0,0 +1,128 @@ +// eslint-disable-next-line prettier/prettier +import type {Job, JobWithId} from './Job'; +import type { IJobParameters } from './types/JobParameters'; +import type { Agenda } from './index'; +/** + * @class + */ +export class JobProcessingQueue { + private _queue: Job[]; + + constructor(private agenda: Agenda) { + this._queue = []; + } + + get length(): number { + return this._queue.length; + } + + getQueue(): Job[] { + return this._queue; + } + + /** + * Pops and returns last queue element (next job to be processed) without checking concurrency. + * @returns {Job} Next Job to be processed + */ + pop(): Job | undefined { + return this._queue.pop(); + } + + /** + * Inserts job in first queue position + * @param {Job} job job to add to queue + * @returns {undefined} + */ + /* + push(job: Job): void { + this._queue.push(job); + } */ + + remove(job: Job): void { + let removeJobIndex = this._queue.indexOf(job); + if (removeJobIndex === -1) { + // lookup by id + removeJobIndex = this._queue.findIndex( + j => j.attrs._id?.toString() === job.attrs._id?.toString() + ); + } + if (removeJobIndex === -1) { + throw new Error(`cannot find job ${job.attrs._id} in processing queue?`); + } + + this._queue.splice(removeJobIndex, 1); + } + + /** + * Inserts job in queue where it will be order from left to right in decreasing + * order of nextRunAt and priority (in case of same nextRunAt), if all values + * are even the first jobs to be introduced will have priority + * @param {Job} job job to add to queue + * @returns {undefined} + */ + insert(job: Job): void { + const matchIndex = this._queue.findIndex(element => { + if ( + element.attrs.nextRunAt && + job.attrs.nextRunAt && + element.attrs.nextRunAt.getTime() <= job.attrs.nextRunAt.getTime() + ) { + if (element.attrs.nextRunAt.getTime() === job.attrs.nextRunAt.getTime()) { + if (element.attrs.priority >= job.attrs.priority) { + return true; + } + } else { + return true; + } + } + + return false; + }); + + if (matchIndex === -1) { + // put on left side of the queue + this._queue.unshift(job); + } else { + this._queue.splice(matchIndex, 0, job); + } + } + + /** + * Returns (does not pop, element remains in queue) first element (always from the right) + * that can be processed (not blocked by concurrency execution) + * @param {Object} jobStatus current status of jobs + * @returns {Job} Next Job to be processed + */ + returnNextConcurrencyFreeJob( + jobStatus: { + [jobName: string]: + | { + running: number; + } + | undefined; + }, + handledJobs: IJobParameters['_id'][] + ): (JobWithId & { attrs: IJobParameters & { nextRunAt?: Date | null } }) | undefined { + const next = (Object.keys(this._queue) as unknown as number[]).reverse().find(i => { + const def = this.agenda.definitions[this._queue[i].attrs.name]; + const status = jobStatus[this._queue[i].attrs.name]; + + // check if we have a definition + // if there is no status available, we are good to go + // if there is no max concurrency defined (0), we are also good to go + // and if concurrency limit is not reached yet (actual running jobs is lower than max concurrency) + if ( + def && + !handledJobs.includes(this._queue[i].attrs._id) && + (!status || !def.concurrency || status.running < def.concurrency) + ) { + return true; + } + return false; + }); + + return next !== undefined + ? (this._queue[next] as JobWithId & { attrs: IJobParameters & { nextRunAt: Date } }) + : undefined; + } +} diff --git a/src/JobProcessor.ts b/src/JobProcessor.ts new file mode 100644 index 0000000..24f11d3 --- /dev/null +++ b/src/JobProcessor.ts @@ -0,0 +1,642 @@ +import * as debug from 'debug'; +import type { IAgendaJobStatus, IAgendaStatus } from './types/AgendaStatus'; +import type { IJobDefinition } from './types/JobDefinition'; +import type { Agenda, JobWithId } from './index'; +import type { IJobParameters } from './types/JobParameters'; +import { Job } from './Job'; +import { JobProcessingQueue } from './JobProcessingQueue'; + +const log = debug('agenda:jobProcessor'); + +// eslint-disable-next-line @typescript-eslint/no-var-requires,global-require +const { version: agendaVersion } = require('../package.json'); + +const MAX_SAFE_32BIT_INTEGER = 2 ** 31; // Math.pow(2,31); + +/** + * @class + * Process methods for jobs + */ +export class JobProcessor { + private jobStatus: { + [name: string]: { running: number; locked: number } | undefined; + } = {}; + + private localQueueProcessing = 0; + + async getStatus(fullDetails = false): Promise { + const jobStatus = Object.keys(this.jobStatus).reduce((obj, key) => { + // eslint-disable-next-line no-param-reassign + obj[key] = { + ...this.jobStatus[key], + config: this.agenda.definitions[key] + }; + return obj; + }, {}) as IAgendaJobStatus; + + return { + version: agendaVersion, + queueName: this.agenda.attrs.name, + totalQueueSizeDB: await this.agenda.db.getQueueSize(), + internal: { + localQueueProcessing: this.localQueueProcessing + }, + config: { + totalLockLimit: this.totalLockLimit, + maxConcurrency: this.maxConcurrency, + processEvery: this.processEvery + }, + jobStatus, + queuedJobs: !fullDetails + ? this.jobQueue.length + : this.jobQueue.getQueue().map(job => ({ + ...job.toJson(), + canceled: job.getCanceledMessage() + })), + runningJobs: !fullDetails + ? this.runningJobs.length + : this.runningJobs.map(job => ({ + ...job.toJson(), + canceled: job.getCanceledMessage() + })), + lockedJobs: !fullDetails + ? this.lockedJobs.length + : this.lockedJobs.map(job => ({ + ...job.toJson(), + canceled: job.getCanceledMessage() + })), + jobsToLock: !fullDetails + ? this.jobsToLock.length + : this.jobsToLock.map(job => ({ + ...job.toJson(), + canceled: job.getCanceledMessage() + })), + isLockingOnTheFly: this.isLockingOnTheFly + }; + } + + private nextScanAt = new Date(); + + private jobQueue: JobProcessingQueue = new JobProcessingQueue(this.agenda); + + private runningJobs: JobWithId[] = []; + + private lockedJobs: JobWithId[] = []; + + private jobsToLock: JobWithId[] = []; + + private isLockingOnTheFly = false; + + private isJobQueueFilling = new Map(); + + private isRunning = true; + + private processInterval?: ReturnType; + + constructor( + private agenda: Agenda, + private maxConcurrency: number, + private totalLockLimit: number, + private processEvery: number + ) { + log('creating interval to call processJobs every [%dms]', processEvery); + this.processInterval = setInterval(() => this.process(), processEvery); + this.process(); + } + + stop(): JobWithId[] { + log.extend('stop')('stop job processor', this.isRunning); + this.isRunning = false; + + if (this.processInterval) { + clearInterval(this.processInterval); + this.processInterval = undefined; + } + + return this.lockedJobs; + } + + // processJobs + async process(extraJob?: JobWithId): Promise { + // Make sure an interval has actually been set + // Prevents race condition with 'Agenda.stop' and already scheduled run + if (!this.isRunning) { + log.extend('process')('JobProcessor got stopped already, returning'); + return; + } + + // Determine whether or not we have a direct process call! + if (!extraJob) { + log.extend('process')('starting to process jobs'); + + // Go through each jobName set in 'Agenda.process' and fill the queue with the next jobs + await Promise.all( + Object.keys(this.agenda.definitions).map(async jobName => { + log.extend('process')('queuing up job to process: [%s]', jobName); + await this.jobQueueFilling(jobName); + }) + ); + this.jobProcessing(); + } else if ( + this.agenda.definitions[extraJob.attrs.name] && + // If the extraJob would have been processed in an older scan, process the job immediately + extraJob.attrs.nextRunAt && + extraJob.attrs.nextRunAt < this.nextScanAt + ) { + log.extend('process')( + '[%s:%s] job would have ran by nextScanAt, processing the job immediately', + extraJob.attrs.name + ); + // Add the job to list of jobs to lock and then lock it immediately! + this.jobsToLock.push(extraJob); + await this.lockOnTheFly(); + } + } + + /** + * Returns true if a job of the specified name can be locked. + * Considers maximum locked jobs at any time if self._lockLimit is > 0 + * Considers maximum locked jobs of the specified name at any time if jobDefinition.lockLimit is > 0 + * @param {String} name name of job to check if we should lock or not + * @returns {boolean} whether or not you should lock job + */ + shouldLock(name: string): boolean { + const jobDefinition = this.agenda.definitions[name]; + let shouldLock = true; + // global lock limit + if (this.totalLockLimit && this.lockedJobs.length >= this.totalLockLimit) { + shouldLock = false; + } + + // job specific lock limit + const status = this.jobStatus[name]; + if (jobDefinition.lockLimit && status && status.locked >= jobDefinition.lockLimit) { + shouldLock = false; + } + + log.extend('shouldLock')( + 'job [%s] lock status: shouldLock = %s', + name, + shouldLock, + `${status?.locked} >= ${jobDefinition?.lockLimit}`, + `${this.lockedJobs.length} >= ${this.totalLockLimit}` + ); + return shouldLock; + } + + /** + * Internal method that adds jobs to be processed to the local queue + * @param {*} jobs Jobs to queue + * @param {boolean} inFront puts the job in front of queue if true + * @returns {undefined} + */ + private enqueueJob(job: Job): void { + this.jobQueue.insert(job); + } + + /** + * Internal method that will lock a job and store it on MongoDB + * This method is called when we immediately start to process a job without using the process interval + * We do this because sometimes jobs are scheduled but will be run before the next process time + * @returns {undefined} + */ + async lockOnTheFly(): Promise { + // Already running this? Return + if (this.isLockingOnTheFly) { + log.extend('lockOnTheFly')('already running, returning'); + return; + } + + // Don't have any jobs to run? Return + if (this.jobsToLock.length === 0) { + log.extend('lockOnTheFly')('no jobs to current lock on the fly, returning'); + return; + } + + this.isLockingOnTheFly = true; + + // Set that we are running this + try { + // Grab a job that needs to be locked + const job = this.jobsToLock.pop(); + + if (job) { + if (this.isJobQueueFilling.has(job.attrs.name)) { + log.extend('lockOnTheFly')('jobQueueFilling already running for: %s', job.attrs.name); + return; + } + + // If locking limits have been hit, stop locking on the fly. + // Jobs that were waiting to be locked will be picked up during a + // future locking interval. + if (!this.shouldLock(job.attrs.name)) { + log.extend('lockOnTheFly')('lock limit hit for: [%s:%S]', job.attrs.name, job.attrs._id); + this.jobsToLock = []; + return; + } + + // Lock the job in MongoDB! + const resp = await this.agenda.db.lockJob(job); + + if (resp) { + if (job.attrs.name !== resp.name) { + throw new Error( + `got different job name: ${resp.name} (actual) !== ${job.attrs.name} (expected)` + ); + } + + const jobToEnqueue = new Job(this.agenda, resp, true) as JobWithId; + + // Before en-queing job make sure we haven't exceed our lock limits + if (!this.shouldLock(jobToEnqueue.attrs.name)) { + log.extend('lockOnTheFly')( + 'lock limit reached while job was locked in database. Releasing lock on [%s]', + jobToEnqueue.attrs.name + ); + this.agenda.db.unlockJob(jobToEnqueue); + + this.jobsToLock = []; + return; + } + + log.extend('lockOnTheFly')( + 'found job [%s:%s] that can be locked on the fly', + jobToEnqueue.attrs.name, + jobToEnqueue.attrs._id + ); + this.updateStatus(jobToEnqueue.attrs.name, 'locked', +1); + this.lockedJobs.push(jobToEnqueue); + this.enqueueJob(jobToEnqueue); + this.jobProcessing(); + } else { + log.extend('lockOnTheFly')('cannot lock job [%s] on the fly', job.attrs.name); + } + } + } finally { + // Mark lock on fly is done for now + this.isLockingOnTheFly = false; + } + + // Re-run in case anything is in the queue + await this.lockOnTheFly(); + } + + private async findAndLockNextJob( + jobName: string, + definition: IJobDefinition + ): Promise { + const lockDeadline = new Date(Date.now().valueOf() - definition.lockLifetime); + log.extend('findAndLockNextJob')( + `looking for lockable jobs for ${jobName} (lock dead line = ${lockDeadline})` + ); + + // Find ONE and ONLY ONE job and set the 'lockedAt' time so that job begins to be processed + const result = await this.agenda.db.getNextJobToRun(jobName, this.nextScanAt, lockDeadline); + + if (result) { + log.extend('findAndLockNextJob')( + 'found a job available to lock, creating a new job on Agenda with id [%s]', + result._id + ); + return new Job(this.agenda, result, true) as JobWithId; + } + + return undefined; + } + + /** + * Internal method used to fill a queue with jobs that can be run + * @param {String} name fill a queue with specific job name + * @returns {undefined} + */ + private async jobQueueFilling(name: string): Promise { + this.isJobQueueFilling.set(name, true); + + try { + // Don't lock because of a limit we have set (lockLimit, etc) + if (!this.shouldLock(name)) { + log.extend('jobQueueFilling')('lock limit reached in queue filling for [%s]', name); + return; + } + + // Set the date of the next time we are going to run _processEvery function + const now = new Date(); + this.nextScanAt = new Date(now.valueOf() + this.processEvery); + + // For this job name, find the next job to run and lock it! + const job = await this.findAndLockNextJob(name, this.agenda.definitions[name]); + + // Still have the job? + // 1. Add it to lock list + // 2. Add count of locked jobs + // 3. Queue the job to actually be run now that it is locked + // 4. Recursively run this same method we are in to check for more available jobs of same type! + if (job) { + if (job.attrs.name !== name) { + throw new Error( + `got different job name: ${job.attrs.name} (actual) !== ${name} (expected)` + ); + } + + // Before en-queing job make sure we haven't exceed our lock limits + if (!this.shouldLock(name)) { + log.extend('jobQueueFilling')( + 'lock limit reached before job was returned. Releasing lock on [%s]', + name + ); + this.agenda.db.unlockJob(job); + return; + } + + log.extend('jobQueueFilling')( + '[%s:%s] job locked while filling queue', + name, + job.attrs._id + ); + this.updateStatus(name, 'locked', +1); + this.lockedJobs.push(job); + + this.enqueueJob(job); + await this.jobQueueFilling(name); + } else { + log.extend('jobQueueFilling')('Cannot lock job [%s]', name); + } + } catch (error) { + log.extend('jobQueueFilling')('[%s] job lock failed while filling queue', name, error); + this.agenda.emit('error', error); + } finally { + this.isJobQueueFilling.delete(name); + } + } + + /** + * Internal method that processes any jobs in the local queue (array) + * handledJobs keeps list of already processed jobs + * @returns {undefined} + */ + private async jobProcessing(handledJobs: IJobParameters['_id'][] = []) { + // Ensure we have jobs + if (this.jobQueue.length === 0) { + return; + } + + this.localQueueProcessing += 1; + + try { + const now = new Date(); + + // Check if there is any job that is not blocked by concurrency + const job = this.jobQueue.returnNextConcurrencyFreeJob(this.jobStatus, handledJobs); + + if (!job) { + log.extend('jobProcessing')('[%s:%s] there is no job to process'); + return; + } + + this.jobQueue.remove(job); + + if (!(await job.isExpired())) { + // check if job has expired (and therefore probably got picked up again by another queue in the meantime) + // before it even has started to run + + log.extend('jobProcessing')( + '[%s:%s] there is a job to process (priority = %d)', + job.attrs.name, + job.attrs._id, + job.attrs.priority, + job.gotTimerToExecute + ); + + // If the 'nextRunAt' time is older than the current time, run the job + // Otherwise, setTimeout that gets called at the time of 'nextRunAt' + if (!job.attrs.nextRunAt || job.attrs.nextRunAt <= now) { + log.extend('jobProcessing')( + '[%s:%s] nextRunAt is in the past, run the job immediately', + job.attrs.name, + job.attrs._id + ); + this.runOrRetry(job); + } else { + const runIn = job.attrs.nextRunAt.getTime() - now.getTime(); + if (runIn > this.processEvery) { + // this job is not in the near future, remove it (it will be picked up later) + log.extend('runOrRetry')( + '[%s:%s] job is too far away, freeing it up', + job.attrs.name, + job.attrs._id + ); + let lockedJobIndex = this.lockedJobs.indexOf(job); + if (lockedJobIndex === -1) { + // lookup by id + lockedJobIndex = this.lockedJobs.findIndex( + j => j.attrs._id?.toString() === job.attrs._id?.toString() + ); + } + if (lockedJobIndex === -1) { + throw new Error(`cannot find job ${job.attrs._id} in locked jobs queue?`); + } + + this.lockedJobs.splice(lockedJobIndex, 1); + this.updateStatus(job.attrs.name, 'locked', -1); + } else { + log.extend('jobProcessing')( + '[%s:%s] nextRunAt is in the future, calling setTimeout(%d)', + job.attrs.name, + job.attrs._id, + runIn + ); + // re add to queue (puts it at the right position in the queue) + this.jobQueue.insert(job); + // ensure every job gets a timer to run at the near future time (but also ensure this time is set only once) + if (!job.gotTimerToExecute) { + job.gotTimerToExecute = true; + setTimeout( + () => { + this.jobProcessing(); + }, + runIn > MAX_SAFE_32BIT_INTEGER ? MAX_SAFE_32BIT_INTEGER : runIn + ); // check if runIn is higher than unsined 32 bit int, if so, use this time to recheck, + // because setTimeout will run in an overflow otherwise and reprocesses immediately + } + } + } + } + + handledJobs.push(job.attrs._id); + + if (job && this.localQueueProcessing < this.maxConcurrency) { + // additionally run again and check if there are more jobs that we can process right now (as long concurrency not reached) + setImmediate(() => this.jobProcessing(handledJobs)); + } + } finally { + this.localQueueProcessing -= 1; + } + } + + /** + * Internal method that tries to run a job and if it fails, retries again! + * @returns {boolean} processed a job or not + */ + private async runOrRetry(job: JobWithId): Promise { + if (!this.isRunning) { + // const a = new Error(); + // console.log('STACK', a.stack); + log.extend('runOrRetry')( + 'JobProcessor got stopped already while calling runOrRetry, returning!' + ); + return; + } + + const jobDefinition = this.agenda.definitions[job.attrs.name]; + const status = this.jobStatus[job.attrs.name]; + + if ( + (!jobDefinition.concurrency || !status || status.running < jobDefinition.concurrency) && + this.runningJobs.length < this.maxConcurrency + ) { + // Add to local "running" queue + this.runningJobs.push(job); + this.updateStatus(job.attrs.name, 'running', 1); + + let jobIsRunning = true; + try { + log.extend('runOrRetry')('[%s:%s] processing job', job.attrs.name, job.attrs._id); + + // check if the job is still alive + const checkIfJobIsStillAlive = () => + // check every "this.agenda.definitions[job.attrs.name].lockLifetime / 2"" (or at mininum every processEvery) + new Promise((resolve, reject) => { + setTimeout(async () => { + // when job is not running anymore, just finish + if (!jobIsRunning) { + log.extend('runOrRetry')( + '[%s:%s] checkIfJobIsStillAlive detected job is not running anymore. stopping check.', + job.attrs.name, + job.attrs._id + ); + resolve(); + return; + } + + if (await job.isExpired()) { + log.extend('runOrRetry')( + '[%s:%s] checkIfJobIsStillAlive detected an expired job, killing it.', + job.attrs.name, + job.attrs._id + ); + + reject( + new Error( + `execution of '${job.attrs.name}' canceled, execution took more than ${ + this.agenda.definitions[job.attrs.name].lockLifetime + }ms. Call touch() for long running jobs to keep them alive.` + ) + ); + return; + } + + if (!job.attrs.lockedAt) { + log.extend('runOrRetry')( + '[%s:%s] checkIfJobIsStillAlive detected a job without a lockedAt value, killing it.', + job.attrs.name, + job.attrs._id + ); + + reject( + new Error( + `execution of '${job.attrs.name}' canceled, no lockedAt date found. Ensure to call touch() for long running jobs to keep them alive.` + ) + ); + return; + } + + resolve(checkIfJobIsStillAlive()); + }, Math.max(this.processEvery / 2, this.agenda.definitions[job.attrs.name].lockLifetime / 2)); + }); + // CALL THE ACTUAL METHOD TO PROCESS THE JOB!!! + await Promise.race([job.run(), checkIfJobIsStillAlive()]); + + log.extend('runOrRetry')( + '[%s:%s] processing job successfull', + job.attrs.name, + job.attrs._id + ); + + // Job isn't in running jobs so throw an error + if (!this.runningJobs.includes(job)) { + log.extend('runOrRetry')( + '[%s] callback was called, job must have been marked as complete already', + job.attrs._id + ); + throw new Error( + `callback already called - job ${job.attrs.name} already marked complete` + ); + } + } catch (error: any) { + job.cancel(error); + log.extend('runOrRetry')( + '[%s:%s] processing job failed', + job.attrs.name, + job.attrs._id, + error + ); + this.agenda.emit('error', error); + } finally { + jobIsRunning = false; + + // Remove the job from the running queue + let runningJobIndex = this.runningJobs.indexOf(job); + if (runningJobIndex === -1) { + // lookup by id + runningJobIndex = this.runningJobs.findIndex( + j => j.attrs._id?.toString() === job.attrs._id?.toString() + ); + } + if (runningJobIndex === -1) { + // eslint-disable-next-line no-unsafe-finally + throw new Error(`cannot find job ${job.attrs._id} in running jobs queue?`); + } + this.runningJobs.splice(runningJobIndex, 1); + this.updateStatus(job.attrs.name, 'running', -1); + + // Remove the job from the locked queue + let lockedJobIndex = this.lockedJobs.indexOf(job); + if (lockedJobIndex === -1) { + // lookup by id + lockedJobIndex = this.lockedJobs.findIndex( + j => j.attrs._id?.toString() === job.attrs._id?.toString() + ); + } + if (lockedJobIndex === -1) { + // eslint-disable-next-line no-unsafe-finally + throw new Error(`cannot find job ${job.attrs._id} in locked jobs queue?`); + } + this.lockedJobs.splice(lockedJobIndex, 1); + this.updateStatus(job.attrs.name, 'locked', -1); + } + + // Re-process jobs now that one has finished + setImmediate(() => this.jobProcessing()); + return; + } + + // Run the job later + log.extend('runOrRetry')( + '[%s:%s] concurrency preventing immediate run, pushing job to top of queue', + job.attrs.name, + job.attrs._id + ); + this.enqueueJob(job); + } + + private updateStatus(name: string, key: 'locked' | 'running', number: -1 | 1) { + if (!this.jobStatus[name]) { + this.jobStatus[name] = { + locked: 0, + running: 0 + }; + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.jobStatus[name]![key] += number; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1b1e876 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,579 @@ +import { EventEmitter } from 'events'; +import * as debug from 'debug'; + +import type { Db, Filter, MongoClientOptions, Sort } from 'mongodb'; +import { SortDirection } from 'mongodb'; +import { ForkOptions } from 'child_process'; +import type { IJobDefinition } from './types/JobDefinition'; +import type { IAgendaConfig } from './types/AgendaConfig'; +import type { IDatabaseOptions, IDbConfig, IMongoOptions } from './types/DbOptions'; +import type { IAgendaStatus } from './types/AgendaStatus'; +import type { IJobParameters } from './types/JobParameters'; +import { Job, JobWithId } from './Job'; +import { JobDbRepository } from './JobDbRepository'; +import { JobPriority, parsePriority } from './utils/priority'; +import { JobProcessor } from './JobProcessor'; +import { calculateProcessEvery } from './utils/processEvery'; +import { getCallerFilePath } from './utils/stack'; + +const log = debug('agenda'); + +const DefaultOptions = { + processEvery: 5000, + defaultConcurrency: 5, + maxConcurrency: 20, + defaultLockLimit: 0, + lockLimit: 0, + defaultLockLifetime: 10 * 60 * 1000, + sort: { nextRunAt: 1, priority: -1 } as const, + forkHelper: { path: 'dist/childWorker.js' } +}; + +/** + * @class + */ +export class Agenda extends EventEmitter { + readonly attrs: IAgendaConfig & IDbConfig; + + public readonly forkedWorker?: boolean; + + public readonly forkHelper?: { + path: string; + options?: ForkOptions; + }; + + db: JobDbRepository; + + // internally used + on(event: 'processJob', listener: (job: JobWithId) => void): this; + + on(event: 'fail', listener: (error: Error, job: JobWithId) => void): this; + on(event: 'success', listener: (job: JobWithId) => void): this; + on(event: 'start', listener: (job: JobWithId) => void): this; + on(event: 'complete', listener: (job: JobWithId) => void): this; + on(event: string, listener: (job: JobWithId) => void): this; + on(event: string, listener: (error: Error, job: JobWithId) => void): this; + on(event: 'ready', listener: () => void): this; + on(event: 'error', listener: (error: Error) => void): this; + on(event: string, listener: (...args) => void): this { + if (this.forkedWorker && event !== 'ready' && event !== 'error') { + const warning = new Error(`calling on(${event}) during a forkedWorker has no effect!`); + console.warn(warning.message, warning.stack); + return this; + } + return super.on(event, listener); + } + + readonly definitions: { + [name: string]: IJobDefinition; + } = {}; + + private jobProcessor?: JobProcessor; + + readonly ready: Promise; + + isActiveJobProcessor(): boolean { + return !!this.jobProcessor; + } + + async runForkedJob(jobId: string) { + const jobData = await this.db.getJobById(jobId); + if (!jobData) { + throw new Error('db entry not found'); + } + const job = new Job(this, jobData); + await job.runJob(); + } + + async getRunningStats(fullDetails = false): Promise { + if (!this.jobProcessor) { + throw new Error('agenda not running!'); + } + return this.jobProcessor.getStatus(fullDetails); + } + + /** + * @param {Object} config - Agenda Config + * @param {Function} cb - Callback after Agenda has started and connected to mongo + */ + constructor( + config: { + name?: string; + defaultConcurrency?: number; + processEvery?: string | number; + maxConcurrency?: number; + defaultLockLimit?: number; + lockLimit?: number; + defaultLockLifetime?: number; + // eslint-disable-next-line @typescript-eslint/ban-types + } & (IDatabaseOptions | IMongoOptions | {}) & + IDbConfig & { + forkHelper?: { path: string; options?: ForkOptions }; + forkedWorker?: boolean; + } = DefaultOptions, + cb?: (error?: Error) => void + ) { + super(); + + this.attrs = { + name: config.name || '', + processEvery: calculateProcessEvery(config.processEvery) || DefaultOptions.processEvery, + defaultConcurrency: config.defaultConcurrency || DefaultOptions.defaultConcurrency, + maxConcurrency: config.maxConcurrency || DefaultOptions.maxConcurrency, + defaultLockLimit: config.defaultLockLimit || DefaultOptions.defaultLockLimit, + lockLimit: config.lockLimit || DefaultOptions.lockLimit, + defaultLockLifetime: config.defaultLockLifetime || DefaultOptions.defaultLockLifetime, // 10 minute default lockLifetime + sort: config.sort || DefaultOptions.sort + }; + + this.forkedWorker = config.forkedWorker; + this.forkHelper = config.forkHelper; + + this.ready = new Promise(resolve => { + this.once('ready', resolve); + }); + + if (this.hasDatabaseConfig(config)) { + this.db = new JobDbRepository(this, config); + this.db.connect(); + } + + if (cb) { + this.ready.then(() => cb()); + } + } + + /** + * Connect to the spec'd MongoDB server and database. + */ + async database( + address: string, + collection?: string, + options?: MongoClientOptions + ): Promise { + this.db = new JobDbRepository(this, { db: { address, collection, options } }); + await this.db.connect(); + return this; + } + + /** + * Use existing mongo connectino to pass into agenda + * @param mongo + * @param collection + */ + async mongo(mongo: Db, collection?: string): Promise { + this.db = new JobDbRepository(this, { mongo, db: { collection } }); + await this.db.connect(); + return this; + } + + /** + * Set the sort query for finding next job + * Default is { nextRunAt: 1, priority: -1 } + * @param query + */ + sort(query: { [key: string]: SortDirection }): Agenda { + log('Agenda.sort([Object])'); + this.attrs.sort = query; + return this; + } + + private hasDatabaseConfig( + config: unknown + ): config is (IDatabaseOptions | IMongoOptions) & IDbConfig { + return !!((config as IDatabaseOptions)?.db?.address || (config as IMongoOptions)?.mongo); + } + + /** + * Cancels any jobs matching the passed MongoDB query, and removes them from the database. + * @param query + */ + async cancel(query: Filter): Promise { + log('attempting to cancel all Agenda jobs', query); + try { + const amountOfRemovedJobs = await this.db.removeJobs(query); + log('%s jobs cancelled', amountOfRemovedJobs); + return amountOfRemovedJobs; + } catch (error) { + log('error trying to delete jobs from MongoDB'); + throw error; + } + } + + /** + * Set name of queue + * @param name + */ + name(name: string): Agenda { + log('Agenda.name(%s)', name); + this.attrs.name = name; + return this; + } + + /** + * Set the time how often the job processor checks for new jobs to process + * @param time + */ + processEvery(time: string | number): Agenda { + if (this.jobProcessor) { + throw new Error( + 'job processor is already running, you need to set processEvery before calling start' + ); + } + log('Agenda.processEvery(%d)', time); + this.attrs.processEvery = calculateProcessEvery(time); + return this; + } + + /** + * Set the concurrency for jobs (globally), type does not matter + * @param num + */ + maxConcurrency(num: number): Agenda { + log('Agenda.maxConcurrency(%d)', num); + this.attrs.maxConcurrency = num; + return this; + } + + /** + * Set the default concurrency for each job + * @param num number of max concurrency + */ + defaultConcurrency(num: number): Agenda { + log('Agenda.defaultConcurrency(%d)', num); + this.attrs.defaultConcurrency = num; + return this; + } + + /** + * Set the default amount jobs that are allowed to be locked at one time (GLOBAL) + * @param num + */ + lockLimit(num: number): Agenda { + log('Agenda.lockLimit(%d)', num); + this.attrs.lockLimit = num; + return this; + } + + /** + * Set default lock limit per job type + * @param num + */ + defaultLockLimit(num: number): Agenda { + log('Agenda.defaultLockLimit(%d)', num); + this.attrs.defaultLockLimit = num; + return this; + } + + /** + * Set the default lock time (in ms) + * Default is 10 * 60 * 1000 ms (10 minutes) + * @param ms + */ + defaultLockLifetime(ms: number): Agenda { + log('Agenda.defaultLockLifetime(%d)', ms); + this.attrs.defaultLockLifetime = ms; + return this; + } + + /** + * Finds all jobs matching 'query' + * @param query + * @param sort + * @param limit + * @param skip + */ + async jobs( + query: Filter = {}, + sort: Sort = {}, + limit = 0, + skip = 0 + ): Promise { + const result = await this.db.getJobs(query, sort, limit, skip); + + return result.map(job => new Job(this, job)); + } + + /** + * Removes all jobs from queue + * @note: Only use after defining your jobs + */ + async purge(): Promise { + const definedNames = Object.keys(this.definitions); + log('Agenda.purge(%o)', definedNames); + return this.cancel({ name: { $not: { $in: definedNames } } }); + } + + /** + * Setup definition for job + * Method is used by consumers of lib to setup their functions + * BREAKING CHANGE in v4: options moved from 2nd to 3rd parameter! + * @param name + * @param processor + * @param options + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + define( + name: string, + processor: (agendaJob: Job, done: (error?: Error) => void) => void, + options?: Partial> & { + priority?: JobPriority; + } + ): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + define( + name: string, + processor: (agendaJob: Job) => Promise, + options?: Partial> & { + priority?: JobPriority; + } + ): void; + define( + name: string, + processor: ((job: Job) => Promise) | ((job: Job, done) => void), + options?: Partial> & { + priority?: JobPriority; + } + ): void { + if (this.definitions[name]) { + log('overwriting already defined agenda job', name); + } + + const filePath = getCallerFilePath(); + + this.definitions[name] = { + fn: processor, + filePath, + concurrency: options?.concurrency || this.attrs.defaultConcurrency, + lockLimit: options?.lockLimit || this.attrs.defaultLockLimit, + priority: parsePriority(options?.priority), + lockLifetime: options?.lockLifetime || this.attrs.defaultLockLifetime + }; + log('job [%s] defined with following options: \n%O', name, this.definitions[name]); + } + + /** + * Internal helper method that uses createJob to create jobs for an array of names + * @param {Number} interval run every X interval + * @param {Array} names Strings of jobs to schedule + * @param {Object} data data to run for job + * @param {Object} options options to run job for + * @returns {Array} array of jobs created + */ + private async createJobs( + names: string[], + createJob: (name: string) => Promise> + ): Promise[]> { + try { + const jobs = await Promise.all(names.map(name => createJob(name))); + + log('createJobs() -> all jobs created successfully'); + + return jobs; + } catch (error) { + log('createJobs() -> error creating one or more of the jobs', error); + throw error; + } + } + + /** + * Given a name and some data, create a new job + * @param name + */ + create(name: string): Job; + create(name: string, data: DATA): Job; + create(name: string, data?: unknown): Job { + log('Agenda.create(%s, [Object])', name); + const priority = this.definitions[name] ? this.definitions[name].priority : 0; + const job = new Job(this, { name, data, type: 'normal', priority }); + return job; + } + + /** + * Creates a scheduled job with given interval and name/names of the job to run + * @param interval + * @param names + * @param data + * @param options + */ + async every( + interval: string | number, + names: string[], + data?: undefined, + options?: { timezone?: string; skipImmediate?: boolean; forkMode?: boolean } + ): Promise[]>; + async every( + interval: string | number, + name: string, + data?: undefined, + options?: { timezone?: string; skipImmediate?: boolean; forkMode?: boolean } + ): Promise>; + async every( + interval: string | number, + names: string[], + data: DATA, + options?: { timezone?: string; skipImmediate?: boolean; forkMode?: boolean } + ): Promise[]>; + async every( + interval: string | number, + name: string, + data: DATA, + options?: { timezone?: string; skipImmediate?: boolean; forkMode?: boolean } + ): Promise>; + async every( + interval: string | number, + names: string | string[], + data?: unknown, + options?: { timezone?: string; skipImmediate?: boolean; forkMode?: boolean } + ): Promise | Job[]> { + /** + * Internal method to setup job that gets run every interval + * @param {Number} interval run every X interval + * @param {String} name String job to schedule + * @param {Object} data data to run for job + * @param {Object} options options to run job for + * @returns {Job} instance of job + */ + log('Agenda.every(%s, %O, %O)', interval, names, options); + + const createJob = async (name: string): Promise => { + const job = this.create(name, data); + job.attrs.type = 'single'; + job.repeatEvery(interval, options); + if (options?.forkMode) { + job.forkMode(options.forkMode); + } + await job.save(); + + return job; + }; + + if (typeof names === 'string') { + const job = await createJob(names); + + return job; + } + + log('Agenda.every(%s, %s, %O)', interval, names, options); + const jobs = await this.createJobs(names, createJob); + + return jobs; + } + + /** + * Schedule a job or jobs at a specific time + * @param when + * @param names + */ + async schedule(when: string | Date, names: string[]): Promise[]>; + async schedule(when: string | Date, names: string): Promise>; + async schedule( + when: string | Date, + names: string[], + data: DATA + ): Promise[]>; + async schedule(when: string | Date, name: string, data: DATA): Promise>; + async schedule( + when: string | Date, + names: string | string[], + data?: unknown + ): Promise { + const createJob = async (name: string) => { + const job = this.create(name, data); + + await job.schedule(when).save(); + + return job; + }; + + if (typeof names === 'string') { + log('Agenda.schedule(%s, %O, [%O])', when, names); + return createJob(names); + } + + log('Agenda.schedule(%s, %O, [%O])', when, names); + return this.createJobs(names, createJob); + } + + /** + * Create a job for this exact moment + * @param name + */ + async now(name: string): Promise>; + async now(name: string, data: DATA): Promise>; + async now(name: string, data?: DATA): Promise> { + log('Agenda.now(%s, [Object])', name); + try { + const job = this.create(name, data); + + job.schedule(new Date()); + await job.save(); + + return job as Job; + } catch (error) { + log('error trying to create a job for this exact moment'); + throw error; + } + } + + /** + * Starts processing jobs using processJobs() methods, storing an interval ID + * This method will only resolve if a db has been set up beforehand. + */ + async start(): Promise { + log( + 'Agenda.start called, waiting for agenda to be initialized (db connection)', + this.attrs.processEvery + ); + await this.ready; + if (this.jobProcessor) { + log('Agenda.start was already called, ignoring'); + return; + } + + this.jobProcessor = new JobProcessor( + this, + this.attrs.maxConcurrency, + this.attrs.lockLimit, + this.attrs.processEvery + ); + + this.on('processJob', this.jobProcessor.process.bind(this.jobProcessor)); + } + + /** + * Clear the interval that processes the jobs and unlocks all currently locked jobs + */ + async stop(): Promise { + if (!this.jobProcessor) { + log('Agenda.stop called, but agenda has never started!'); + return; + } + + log('Agenda.stop called, clearing interval for processJobs()'); + + const lockedJobs = this.jobProcessor.stop(); + + log('Agenda._unlockJobs()'); + const jobIds = lockedJobs?.map(job => job.attrs._id) || []; + + if (jobIds.length > 0) { + log('about to unlock jobs with ids: %O', jobIds); + await this.db.unlockJobs(jobIds); + } + + this.off('processJob', this.jobProcessor.process.bind(this.jobProcessor)); + + this.jobProcessor = undefined; + } +} + +export * from './types/AgendaConfig'; + +export * from './types/JobDefinition'; + +export * from './types/JobParameters'; + +export * from './types/DbOptions'; + +export * from './Job'; diff --git a/src/types/AgendaConfig.ts b/src/types/AgendaConfig.ts new file mode 100644 index 0000000..90909b2 --- /dev/null +++ b/src/types/AgendaConfig.ts @@ -0,0 +1,15 @@ +export interface IAgendaConfig { + name?: string; + + defaultConcurrency: number; + + processEvery: number; + + maxConcurrency: number; + + defaultLockLimit: number; + + lockLimit: number; + + defaultLockLifetime: number; +} diff --git a/src/types/AgendaStatus.ts b/src/types/AgendaStatus.ts new file mode 100644 index 0000000..a8ace1d --- /dev/null +++ b/src/types/AgendaStatus.ts @@ -0,0 +1,30 @@ +import type { IJobParameters } from './JobParameters'; +import type { IJobDefinition } from './JobDefinition'; + +export interface IAgendaJobStatus { + [name: string]: { + running: number; + locked: number; + config: IJobDefinition; + }; +} + +export interface IAgendaStatus { + version: string; + queueName: string | undefined; + totalQueueSizeDB: number; + config: { + totalLockLimit: number; + maxConcurrency: number; + processEvery: string | number; + }; + internal: { + localQueueProcessing: number; + }; + jobStatus?: IAgendaJobStatus; + queuedJobs: number | IJobParameters[]; + runningJobs: number | IJobParameters[]; + lockedJobs: number | IJobParameters[]; + jobsToLock: number | IJobParameters[]; + isLockingOnTheFly: boolean; +} diff --git a/src/types/DbOptions.ts b/src/types/DbOptions.ts new file mode 100644 index 0000000..3cf3dc2 --- /dev/null +++ b/src/types/DbOptions.ts @@ -0,0 +1,23 @@ +import type { Db, MongoClientOptions, SortDirection } from 'mongodb'; + +export interface IDatabaseOptions { + db: { + collection?: string; + address: string; + options?: MongoClientOptions; + }; +} + +export interface IMongoOptions { + db?: { + collection?: string; + }; + mongo: Db; +} + +export interface IDbConfig { + ensureIndex?: boolean; + sort?: { + [key: string]: SortDirection; + }; +} diff --git a/src/types/JobDefinition.ts b/src/types/JobDefinition.ts new file mode 100644 index 0000000..4ab45bd --- /dev/null +++ b/src/types/JobDefinition.ts @@ -0,0 +1,20 @@ +import type { Job } from '../Job'; + +export interface IJobDefinition { + /** max number of locked jobs of this kind */ + lockLimit: number; + /** lock lifetime in milliseconds */ + lockLifetime: number; + /** Higher priority jobs will run first. */ + priority?: number; + /** how many jobs of this kind can run in parallel/simultanously per Agenda instance */ + concurrency?: number; + + filePath: string | undefined; + fn: DefinitionProcessor void)>; +} + +export type DefinitionProcessor = ( + agendaJob: Job, + done: CB +) => CB extends void ? Promise : void; diff --git a/src/types/JobParameters.ts b/src/types/JobParameters.ts new file mode 100644 index 0000000..a7e2e66 --- /dev/null +++ b/src/types/JobParameters.ts @@ -0,0 +1,51 @@ +import { Filter, ObjectId } from 'mongodb'; + +export interface IJobParameters { + _id?: ObjectId; + + name: string; + priority: number; + nextRunAt: Date | null; + /** + * normal: job is queued and will be processed (regular case when the user adds a new job) + * single: job with this name is only queued once, if there is an exisitn gentry in the database, the job is just updated, but not newly inserted (this is used for .every()) + */ + type: 'normal' | 'single'; + + lockedAt?: Date; + lastFinishedAt?: Date; + failedAt?: Date; + failCount?: number; + failReason?: string; + repeatTimezone?: string; + lastRunAt?: Date; + repeatInterval?: string | number; + data: DATA; + repeatAt?: string; + disabled?: boolean; + progress?: number; + + // unique query object + unique?: Filter, 'unique'>>; + uniqueOpts?: { + insertOnly: boolean; + }; + + lastModifiedBy?: string; + + /** forks a new node sub process for executing this job */ + fork?: boolean; +} + +export type TJobDatefield = keyof Pick< + IJobParameters, + 'lastRunAt' | 'lastFinishedAt' | 'nextRunAt' | 'failedAt' | 'lockedAt' +>; + +export const datefields: Array = [ + 'lastRunAt', + 'lastFinishedAt', + 'nextRunAt', + 'failedAt', + 'lockedAt' +]; diff --git a/src/utils/hasMongoProtocol.ts b/src/utils/hasMongoProtocol.ts new file mode 100644 index 0000000..ec21e5a --- /dev/null +++ b/src/utils/hasMongoProtocol.ts @@ -0,0 +1 @@ +export const hasMongoProtocol = (url: string): boolean => /mongodb(?:\+srv)?:\/\/.*/.test(url); diff --git a/src/utils/isValidDate.ts b/src/utils/isValidDate.ts new file mode 100644 index 0000000..ae81892 --- /dev/null +++ b/src/utils/isValidDate.ts @@ -0,0 +1,4 @@ +export function isValidDate(date: unknown): date is Date { + // An invalid date object returns NaN for getTime() + return date !== null && Number.isNaN(new Date(date as string).getTime()) === false; +} diff --git a/src/utils/nextRunAt.ts b/src/utils/nextRunAt.ts new file mode 100644 index 0000000..8e82c1c --- /dev/null +++ b/src/utils/nextRunAt.ts @@ -0,0 +1,107 @@ +/* eslint-disable import/first */ +import { DateTime } from 'luxon'; +import * as date from 'date.js'; +import * as debug from 'debug'; +import { parseExpression } from 'cron-parser'; +import humanInterval = require('human-interval'); +import { isValidDate } from './isValidDate'; +import type { IJobParameters } from '../types/JobParameters'; + +const log = debug('agenda:nextRunAt'); + +const dateForTimezone = (timezoneDate: Date, timezone?: string): DateTime => + DateTime.fromJSDate(timezoneDate, { zone: timezone }); + +export function isValidHumanInterval(value: unknown): value is string { + const transformedValue = humanInterval(value as string); + return typeof transformedValue === 'number' && Number.isNaN(transformedValue) === false; +} + +/** + * Internal method that computes the interval + */ +export const computeFromInterval = (attrs: IJobParameters): Date => { + const previousNextRunAt = attrs.nextRunAt || new Date(); + log('[%s:%s] computing next run via interval [%s]', attrs.name, attrs._id, attrs.repeatInterval); + + const lastRun = dateForTimezone(attrs.lastRunAt || new Date(), attrs.repeatTimezone); + + const cronOptions = { + currentDate: lastRun.toJSDate(), + tz: attrs.repeatTimezone + }; + + let nextRunAt: Date | null = null; + + let error; + if (typeof attrs.repeatInterval === 'string') { + try { + let cronTime = parseExpression(attrs.repeatInterval, cronOptions); + let nextDate = cronTime.next().toDate(); + if ( + nextDate.valueOf() === lastRun.valueOf() || + nextDate.valueOf() <= previousNextRunAt.valueOf() + ) { + // Handle cronTime giving back the same date for the next run time + cronOptions.currentDate = new Date(lastRun.valueOf() + 1000); + cronTime = parseExpression(attrs.repeatInterval, cronOptions); + nextDate = cronTime.next().toDate(); + } + + nextRunAt = nextDate; + + // eslint-disable-next-line no-empty + } catch (err) { + error = err; + } + } + + if (isValidHumanInterval(attrs.repeatInterval)) { + if (!attrs.lastRunAt) { + nextRunAt = new Date(lastRun.valueOf()); + } else { + const intervalValue = humanInterval(attrs.repeatInterval) as number; + nextRunAt = new Date(lastRun.valueOf() + intervalValue); + } + } + + if (!isValidDate(nextRunAt)) { + log( + '[%s:%s] failed to calculate nextRunAt due to invalid repeat interval', + attrs.name, + attrs._id + ); + throw new Error( + `failed to calculate nextRunAt due to invalid repeat interval (${attrs.repeatInterval}): ${ + error || 'no readable human interval' + }` + ); + } + + return nextRunAt; +}; + +/** + * Internal method to compute next run time from the repeat string + * @returns {undefined} + */ +export function computeFromRepeatAt(attrs: IJobParameters): Date { + const lastRun = attrs.lastRunAt || new Date(); + const nextDate = date(attrs.repeatAt).valueOf(); + + // If you do not specify offset date for below test it will fail for ms + const offset = Date.now(); + + if (offset === date(attrs.repeatAt, offset).valueOf()) { + log('[%s:%s] failed to calculate repeatAt due to invalid format', attrs.name, attrs._id); + // this.attrs.nextRunAt = undefined; + // this.fail('failed to calculate repeatAt time due to invalid format'); + throw new Error('failed to calculate repeatAt time due to invalid format'); + } + + if (nextDate.valueOf() === lastRun.valueOf()) { + return date('tomorrow at ', attrs.repeatAt); + } + + return date(attrs.repeatAt); +} diff --git a/src/utils/priority.ts b/src/utils/priority.ts new file mode 100644 index 0000000..dfdd7da --- /dev/null +++ b/src/utils/priority.ts @@ -0,0 +1,24 @@ +export type JobPriority = number | keyof typeof priorityMap; + +const priorityMap = { + lowest: -20, + low: -10, + normal: 0, + high: 10, + highest: 20 +}; + +/** + * Internal method to turn priority into a number + */ +export function parsePriority(priority?: JobPriority): number { + if (typeof priority === 'number') { + return priority; + } + + if (typeof priority === 'string' && priorityMap[priority]) { + return priorityMap[priority]; + } + + return priorityMap.normal; +} diff --git a/src/utils/processEvery.ts b/src/utils/processEvery.ts new file mode 100644 index 0000000..ca341b1 --- /dev/null +++ b/src/utils/processEvery.ts @@ -0,0 +1,6 @@ +import humanInterval = require('human-interval'); + +export function calculateProcessEvery(input: number | string = 5000): number { + if (typeof input === 'number') return input; + return (humanInterval(input) as number) || 5000; +} diff --git a/src/utils/stack.ts b/src/utils/stack.ts new file mode 100644 index 0000000..e42b4f0 --- /dev/null +++ b/src/utils/stack.ts @@ -0,0 +1,21 @@ +export function getCallerFilePath(position = 2): string | undefined { + if (position >= Error.stackTraceLimit) { + throw new TypeError( + `getCallerFile(position) requires position be less then Error.stackTraceLimit but position was: \`${position}\` and Error.stackTraceLimit was: \`${Error.stackTraceLimit}\`` + ); + } + + const oldPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = (_, stack) => stack; + // eslint-disable-next-line unicorn/error-message + const { stack } = new Error(); + Error.prepareStackTrace = oldPrepareStackTrace; + + if (stack !== null && typeof stack === 'object') { + // stack[0] holds this file + // stack[1] holds where this function was called + // stack[2] holds the file we're interested in + return stack[position] ? (stack[position] as any).getFileName() : undefined; + } + return undefined; +} diff --git a/test/agenda.test.ts b/test/agenda.test.ts new file mode 100644 index 0000000..60f088c --- /dev/null +++ b/test/agenda.test.ts @@ -0,0 +1,782 @@ +/* eslint-disable no-console,no-unused-expressions,@typescript-eslint/no-unused-expressions */ + +import * as delay from 'delay'; +import { Db } from 'mongodb'; +import { expect } from 'chai'; +import { mockMongo } from './helpers/mock-mongodb'; + +import { Agenda } from '../src'; +import { hasMongoProtocol } from '../src/utils/hasMongoProtocol'; +import { Job } from '../src/Job'; + +// agenda instances +let globalAgenda: Agenda; +// connection string to mongodb +let mongoCfg: string; +// mongo db connection db instance +let mongoDb: Db; + +const clearJobs = async (): Promise => { + if (mongoDb) { + await mongoDb.collection('agendaJobs').deleteMany({}); + } +}; + +// Slow timeouts for Travis +const jobTimeout = 500; +const jobType = 'do work'; +const jobProcessor = () => {}; + +describe('Agenda', () => { + beforeEach(async () => { + if (!mongoDb) { + const mockedMongo = await mockMongo(); + mongoCfg = mockedMongo.uri; + mongoDb = mockedMongo.mongo.db(); + } + + return new Promise(resolve => { + globalAgenda = new Agenda( + { + mongo: mongoDb + }, + async () => { + await delay(50); + await clearJobs(); + globalAgenda.define('someJob', jobProcessor); + globalAgenda.define('send email', jobProcessor); + globalAgenda.define('some job', jobProcessor); + globalAgenda.define(jobType, jobProcessor); + return resolve(); + } + ); + }); + }); + + afterEach(async () => { + await delay(50); + if (globalAgenda) { + await globalAgenda.stop(); + await clearJobs(); + } + // await mongoClient.disconnect(); + // await jobs._db.close(); + }); + + it('sets a default processEvery', () => { + expect(globalAgenda.attrs.processEvery).to.equal(5000); + }); + + describe('configuration methods', () => { + it('sets the _db directly when passed as an option', () => { + const agendaDb = new Agenda({ mongo: mongoDb }); + expect(agendaDb.db).to.not.equal(undefined); + }); + }); + + describe('configuration methods', () => { + describe('mongo connection tester', () => { + it('passing a valid server connection string', () => { + expect(hasMongoProtocol(mongoCfg)).to.equal(true); + }); + + it('passing a valid multiple server connection string', () => { + expect(hasMongoProtocol(`mongodb+srv://localhost/agenda-test`)).to.equal(true); + }); + + it('passing an invalid connection string', () => { + expect(hasMongoProtocol(`localhost/agenda-test`)).to.equal(false); + }); + }); + describe('mongo', () => { + it('sets the _db directly', () => { + const agenda = new Agenda(); + agenda.mongo(mongoDb); + expect(agenda.db).to.not.equal(undefined); + }); + + it('returns itself', async () => { + const agenda = new Agenda(); + expect(await agenda.mongo(mongoDb)).to.equal(agenda); + }); + }); + + describe('name', () => { + it('sets the agenda name', () => { + globalAgenda.name('test queue'); + expect(globalAgenda.attrs.name).to.equal('test queue'); + }); + it('returns itself', () => { + expect(globalAgenda.name('test queue')).to.equal(globalAgenda); + }); + }); + describe('processEvery', () => { + it('sets the processEvery time', () => { + globalAgenda.processEvery('3 minutes'); + expect(globalAgenda.attrs.processEvery).to.equal(180000); + }); + it('returns itself', () => { + expect(globalAgenda.processEvery('3 minutes')).to.equal(globalAgenda); + }); + }); + describe('maxConcurrency', () => { + it('sets the maxConcurrency', () => { + globalAgenda.maxConcurrency(10); + expect(globalAgenda.attrs.maxConcurrency).to.equal(10); + }); + it('returns itself', () => { + expect(globalAgenda.maxConcurrency(10)).to.equal(globalAgenda); + }); + }); + describe('defaultConcurrency', () => { + it('sets the defaultConcurrency', () => { + globalAgenda.defaultConcurrency(1); + expect(globalAgenda.attrs.defaultConcurrency).to.equal(1); + }); + it('returns itself', () => { + expect(globalAgenda.defaultConcurrency(5)).to.equal(globalAgenda); + }); + }); + describe('lockLimit', () => { + it('sets the lockLimit', () => { + globalAgenda.lockLimit(10); + expect(globalAgenda.attrs.lockLimit).to.equal(10); + }); + it('returns itself', () => { + expect(globalAgenda.lockLimit(10)).to.equal(globalAgenda); + }); + }); + describe('defaultLockLimit', () => { + it('sets the defaultLockLimit', () => { + globalAgenda.defaultLockLimit(1); + expect(globalAgenda.attrs.defaultLockLimit).to.equal(1); + }); + it('returns itself', () => { + expect(globalAgenda.defaultLockLimit(5)).to.equal(globalAgenda); + }); + }); + describe('defaultLockLifetime', () => { + it('returns itself', () => { + expect(globalAgenda.defaultLockLifetime(1000)).to.equal(globalAgenda); + }); + it('sets the default lock lifetime', () => { + globalAgenda.defaultLockLifetime(9999); + expect(globalAgenda.attrs.defaultLockLifetime).to.equal(9999); + }); + it('is inherited by jobs', () => { + globalAgenda.defaultLockLifetime(7777); + globalAgenda.define('testDefaultLockLifetime', () => {}); + expect(globalAgenda.definitions.testDefaultLockLifetime.lockLifetime).to.equal(7777); + }); + }); + describe('sort', () => { + it('returns itself', () => { + expect(globalAgenda.sort({ nextRunAt: 1, priority: -1 })).to.equal(globalAgenda); + }); + it('sets the default sort option', () => { + globalAgenda.sort({ nextRunAt: -1 }); + expect(globalAgenda.attrs.sort).to.eql({ nextRunAt: -1 }); + }); + }); + }); + + describe('job methods', () => { + describe('create', () => { + let job; + beforeEach(() => { + job = globalAgenda.create('sendEmail', { to: 'some guy' }); + }); + + it('returns a job', () => { + expect(job).to.to.be.an.instanceof(Job); + }); + it('sets the name', () => { + expect(job.attrs.name).to.equal('sendEmail'); + }); + it('sets the type', () => { + expect(job.attrs.type).to.equal('normal'); + }); + it('sets the agenda', () => { + expect(job.agenda).to.equal(globalAgenda); + }); + it('sets the data', () => { + expect(job.attrs.data).to.have.property('to', 'some guy'); + }); + }); + + describe('define', () => { + it('stores the definition for the job', () => { + expect(globalAgenda.definitions.someJob).to.have.property('fn', jobProcessor); + }); + + it('sets the default concurrency for the job', () => { + expect(globalAgenda.definitions.someJob).to.have.property('concurrency', 5); + }); + + it('sets the default lockLimit for the job', () => { + expect(globalAgenda.definitions.someJob).to.have.property('lockLimit', 0); + }); + + it('sets the default priority for the job', () => { + expect(globalAgenda.definitions.someJob).to.have.property('priority', 0); + }); + it('takes concurrency option for the job', () => { + globalAgenda.define('highPriority', jobProcessor, { priority: 10 }); + expect(globalAgenda.definitions.highPriority).to.have.property('priority', 10); + }); + }); + + describe('every', () => { + describe('with a job name specified', () => { + it('returns a job', async () => { + expect(await globalAgenda.every('5 minutes', 'send email')).to.be.an.instanceof(Job); + }); + it('sets the repeatEvery', async () => { + expect( + await globalAgenda + .every('5 seconds', 'send email') + .then(({ attrs }) => attrs.repeatInterval) + ).to.equal('5 seconds'); + }); + it('sets the agenda', async () => { + expect( + await globalAgenda.every('5 seconds', 'send email').then(({ agenda }) => agenda) + ).to.equal(globalAgenda); + }); + it('should update a job that was previously scheduled with `every`', async () => { + await globalAgenda.every(10, 'shouldBeSingleJob'); + await delay(10); + await globalAgenda.every(20, 'shouldBeSingleJob'); + + // Give the saves a little time to propagate + await delay(jobTimeout); + + const res = await globalAgenda.jobs({ name: 'shouldBeSingleJob' }); + expect(res).to.have.length(1); + }); + it('should not run immediately if options.skipImmediate is true', async () => { + const jobName = 'send email'; + await globalAgenda.every('5 minutes', jobName, {}, { skipImmediate: true }); + const job = (await globalAgenda.jobs({ name: jobName }))[0]; + const nextRunAt = job.attrs.nextRunAt!.getTime(); + const now = new Date().getTime(); + expect(nextRunAt - now > 0).to.equal(true); + }); + it('should run immediately if options.skipImmediate is false', async () => { + const jobName = 'send email'; + await globalAgenda.every('5 minutes', jobName, {}, { skipImmediate: false }); + const job = (await globalAgenda.jobs({ name: jobName }))[0]; + const nextRunAt = job.attrs.nextRunAt!.getTime(); + const now = new Date().getTime(); + expect(nextRunAt - now <= 0).to.equal(true); + }); + }); + describe('with array of names specified', () => { + it('returns array of jobs', async () => { + expect(await globalAgenda.every('5 minutes', ['send email', 'some job'])).to.be.an( + 'array' + ); + }); + }); + }); + + describe('schedule', () => { + describe('with a job name specified', () => { + it('returns a job', async () => { + expect(await globalAgenda.schedule('in 5 minutes', 'send email')).to.be.an.instanceof( + Job + ); + }); + it('sets the schedule', async () => { + const fiveish = new Date().valueOf() + 250000; + const scheduledJob = await globalAgenda.schedule('in 5 minutes', 'send email'); + expect(scheduledJob.attrs.nextRunAt!.valueOf()).to.be.greaterThan(fiveish); + }); + }); + describe('with array of names specified', () => { + it('returns array of jobs', async () => { + expect(await globalAgenda.schedule('5 minutes', ['send email', 'some job'])).to.be.an( + 'array' + ); + }); + }); + }); + + describe('unique', () => { + describe('should demonstrate unique contraint', () => { + it('should modify one job when unique matches', async () => { + const job1 = await globalAgenda + .create('unique job', { + type: 'active', + userId: '123', + other: true + }) + .unique({ + 'data.type': 'active', + 'data.userId': '123' + }) + .schedule('now') + .save(); + + await delay(100); + + const job2 = await globalAgenda + .create('unique job', { + type: 'active', + userId: '123', + other: false + }) + .unique({ + 'data.type': 'active', + 'data.userId': '123' + }) + .schedule('now') + .save(); + + expect(job1.attrs.nextRunAt!.toISOString()).not.to.equal( + job2.attrs.nextRunAt!.toISOString() + ); + + mongoDb + .collection('agendaJobs') + .find({ + name: 'unique job' + }) + .toArray((err, jobs) => { + if (err) { + throw err; + } + + expect(jobs).to.have.length(1); + }); + }); + + it('should not modify job when unique matches and insertOnly is set to true', async () => { + const job1 = await globalAgenda + .create('unique job', { + type: 'active', + userId: '123', + other: true + }) + .unique( + { + 'data.type': 'active', + 'data.userId': '123' + }, + { + insertOnly: true + } + ) + .schedule('now') + .save(); + + const job2 = await globalAgenda + .create('unique job', { + type: 'active', + userId: '123', + other: false + }) + .unique( + { + 'data.type': 'active', + 'data.userId': '123' + }, + { + insertOnly: true + } + ) + .schedule('now') + .save(); + + expect(job1.attrs.nextRunAt!.toISOString()).to.equal(job2.attrs.nextRunAt!.toISOString()); + + mongoDb + .collection('agendaJobs') + .find({ + name: 'unique job' + }) + .toArray((err, jobs) => { + if (err) { + throw err; + } + + expect(jobs).to.have.length(1); + }); + }); + }); + + describe('should demonstrate non-unique contraint', () => { + it("should create two jobs when unique doesn't match", async () => { + const time = new Date(Date.now() + 1000 * 60 * 3); + const time2 = new Date(Date.now() + 1000 * 60 * 4); + + await globalAgenda + .create('unique job', { + type: 'active', + userId: '123', + other: true + }) + .unique({ + 'data.type': 'active', + 'data.userId': '123', + nextRunAt: time + }) + .schedule(time) + .save(); + + await globalAgenda + .create('unique job', { + type: 'active', + userId: '123', + other: false + }) + .unique({ + 'data.type': 'active', + 'data.userId': '123', + nextRunAt: time2 + }) + .schedule(time) + .save(); + + mongoDb + .collection('agendaJobs') + .find({ + name: 'unique job' + }) + .toArray((err, jobs) => { + if (err) { + throw err; + } + + expect(jobs).to.have.length(2); + }); + }); + }); + }); + + describe('now', () => { + it('returns a job', async () => { + expect(await globalAgenda.now('send email')).to.to.be.an.instanceof(Job); + }); + it('sets the schedule', async () => { + const now = new Date(); + expect( + await globalAgenda.now('send email').then(({ attrs }) => attrs.nextRunAt!.valueOf()) + ).to.greaterThan(now.valueOf() - 1); + }); + + it('runs the job immediately', async () => { + globalAgenda.define('immediateJob', async job => { + expect(await job.isRunning()).to.be.equal(true); + await globalAgenda.stop(); + }); + await globalAgenda.now('immediateJob'); + await globalAgenda.start(); + }); + }); + + describe('jobs', () => { + it('returns jobs', async () => { + await globalAgenda.create('test').save(); + const c = await globalAgenda.jobs({}); + + expect(c.length).to.not.equals(0); + expect(c[0]).to.to.be.an.instanceof(Job); + await clearJobs(); + }); + }); + + describe('purge', () => { + it('removes all jobs without definitions', async () => { + const job = globalAgenda.create('no definition'); + await globalAgenda.stop(); + await job.save(); + const j = await globalAgenda.jobs({ + name: 'no definition' + }); + + expect(j).to.have.length(1); + await globalAgenda.purge(); + const jAfterPurge = await globalAgenda.jobs({ + name: 'no definition' + }); + + expect(jAfterPurge).to.have.length(0); + }); + }); + + describe('saveJob', () => { + it('persists job to the database', async () => { + const job = globalAgenda.create('someJob', {}); + await job.save(); + + expect(job.attrs._id).to.not.be.equal(undefined); + + await clearJobs(); + }); + }); + }); + + describe('cancel', () => { + beforeEach(async () => { + let remaining = 3; + const checkDone = () => { + remaining -= 1; + }; + + await globalAgenda.create('jobA').save().then(checkDone); + await globalAgenda.create('jobA', 'someData').save().then(checkDone); + await globalAgenda.create('jobB').save().then(checkDone); + expect(remaining).to.equal(0); + }); + + afterEach(async () => { + await globalAgenda.db.removeJobs({ name: { $in: ['jobA', 'jobB'] } }); + }); + + it('should cancel a job', async () => { + const j = await globalAgenda.jobs({ name: 'jobA' }); + expect(j).to.have.length(2); + + await globalAgenda.cancel({ name: 'jobA' }); + const job = await globalAgenda.jobs({ name: 'jobA' }); + + expect(job).to.have.length(0); + }); + + it('should cancel multiple jobs', async () => { + const jobs1 = await globalAgenda.jobs({ name: { $in: ['jobA', 'jobB'] } }); + expect(jobs1).to.have.length(3); + await globalAgenda.cancel({ name: { $in: ['jobA', 'jobB'] } }); + + const jobs2 = await globalAgenda.jobs({ name: { $in: ['jobA', 'jobB'] } }); + expect(jobs2).to.have.length(0); + }); + + it('should cancel jobs only if the data matches', async () => { + const jobs1 = await globalAgenda.jobs({ name: 'jobA', data: 'someData' }); + expect(jobs1).to.have.length(1); + await globalAgenda.cancel({ name: 'jobA', data: 'someData' }); + + const jobs2 = await globalAgenda.jobs({ name: 'jobA', data: 'someData' }); + expect(jobs2).to.have.length(0); + + const jobs3 = await globalAgenda.jobs({ name: 'jobA' }); + expect(jobs3).to.have.length(1); + }); + }); + + describe('search', () => { + beforeEach(async () => { + await globalAgenda.create('jobA', 1).save(); + await globalAgenda.create('jobA', 2).save(); + await globalAgenda.create('jobA', 3).save(); + }); + + afterEach(async () => { + await globalAgenda.db.removeJobs({ name: 'jobA' }); + }); + + it('should limit jobs', async () => { + const results = await globalAgenda.jobs({ name: 'jobA' }, {}, 2); + expect(results).to.have.length(2); + }); + + it('should skip jobs', async () => { + const results = await globalAgenda.jobs({ name: 'jobA' }, {}, 2, 2); + expect(results).to.have.length(1); + }); + + it('should sort jobs', async () => { + const results = await globalAgenda.jobs({ name: 'jobA' }, { data: -1 }); + + expect(results).to.have.length(3); + + const job1 = results[0]; + const job2 = results[1]; + const job3 = results[2]; + + expect(job1.attrs.data).to.equal(3); + expect(job2.attrs.data).to.equal(2); + expect(job3.attrs.data).to.equal(1); + }); + }); + + describe('ensureIndex findAndLockNextJobIndex', () => { + it('ensureIndex-Option false does not create index findAndLockNextJobIndex', async () => { + const agenda = new Agenda({ + mongo: mongoDb, + ensureIndex: false + }); + + agenda.define('someJob', jobProcessor); + await agenda.create('someJob', 1).save(); + + const listIndex = await mongoDb.command({ listIndexes: 'agendaJobs' }); + expect(listIndex.cursor.firstBatch).to.have.lengthOf(1); + expect(listIndex.cursor.firstBatch[0].name).to.be.equal('_id_'); + }); + + it('ensureIndex-Option true does create index findAndLockNextJobIndex', async () => { + const agenda = new Agenda({ + mongo: mongoDb, + ensureIndex: true + }); + + agenda.define('someJob', jobProcessor); + await agenda.create('someJob', 1).save(); + + const listIndex = await mongoDb.command({ listIndexes: 'agendaJobs' }); + expect(listIndex.cursor.firstBatch).to.have.lengthOf(2); + expect(listIndex.cursor.firstBatch[0].name).to.be.equal('_id_'); + expect(listIndex.cursor.firstBatch[1].name).to.be.equal('findAndLockNextJobIndex'); + }); + + it('creating two agenda-instances with ensureIndex-Option true does not throw an error', async () => { + const agenda = new Agenda({ + mongo: mongoDb, + ensureIndex: true + }); + + agenda.define('someJob', jobProcessor); + await agenda.create('someJob', 1).save(); + + const secondAgenda = new Agenda({ + mongo: mongoDb, + ensureIndex: true + }); + + secondAgenda.define('someJob', jobProcessor); + await secondAgenda.create('someJob', 1).save(); + }); + }); + + describe('process jobs', () => { + // eslint-disable-line prefer-arrow-callback + it('do not run failed jobs again', async () => { + const unhandledRejections: any[] = []; + const rejectionsHandler = error => unhandledRejections.push(error); + process.on('unhandledRejection', rejectionsHandler); + + let jprocesses = 0; + + globalAgenda.define('failing job', async _job => { + jprocesses++; + throw new Error('failed'); + }); + + let failCalled = false; + globalAgenda.on('fail:failing job', _err => { + failCalled = true; + }); + + let errorCalled = false; + globalAgenda.on('error', _err => { + errorCalled = true; + }); + + globalAgenda.processEvery(100); + await globalAgenda.start(); + + await globalAgenda.now('failing job'); + + await delay(500); + + process.removeListener('unhandledRejection', rejectionsHandler); + + expect(jprocesses).to.be.equal(1); + expect(errorCalled).to.be.false; + expect(failCalled).to.be.true; + expect(unhandledRejections).to.have.length(0); + }).timeout(10000); + + // eslint-disable-line prefer-arrow-callback + it('ensure there is no unhandledPromise on job timeouts', async () => { + const unhandledRejections: any[] = []; + const rejectionsHandler = error => unhandledRejections.push(error); + process.on('unhandledRejection', rejectionsHandler); + + globalAgenda.define( + 'very short timeout', + (_job, done) => { + setTimeout(() => { + done(); + }, 10000); + }, + { + lockLifetime: 100 + } + ); + + let errorCalled = false; + globalAgenda.on('error', _err => { + errorCalled = true; + }); + + globalAgenda.processEvery(100); + await globalAgenda.start(); + + // await globalAgenda.every('1 seconds', 'j0'); + await globalAgenda.now('very short timeout'); + + await delay(500); + + process.removeListener('unhandledRejection', rejectionsHandler); + + expect(errorCalled).to.be.true; + expect(unhandledRejections).to.have.length(0); + }).timeout(10000); + + it('should not cause unhandledRejection', async () => { + // This unit tests if for this bug [https://github.com/agenda/agenda/issues/884] + // which is not reproducible with default agenda config on shorter processEvery. + // Thus we set the test timeout to 10000, and the delay below to 6000. + + const unhandledRejections: any[] = []; + const rejectionsHandler = error => unhandledRejections.push(error); + process.on('unhandledRejection', rejectionsHandler); + + /* + let j0processes = 0; + globalAgenda.define('j0', (_job, done) => { + j0processes += 1; + done(); + }); */ + + let j1processes = 0; + + globalAgenda.define('j1', (_job, done) => { + j1processes += 1; + done(); + }); + + let j2processes = 0; + globalAgenda.define('j2', (_job, done) => { + j2processes += 1; + done(); + }); + + let j3processes = 0; + globalAgenda.define('j3', async _job => { + j3processes += 1; + }); + await globalAgenda.start(); + + // await globalAgenda.every('1 seconds', 'j0'); + await globalAgenda.every('5 seconds', 'j1'); + await globalAgenda.every('10 seconds', 'j2'); + await globalAgenda.every('15 seconds', 'j3'); + + await delay(3001); + + process.removeListener('unhandledRejection', rejectionsHandler); + + // expect(j0processes).to.equal(5); + expect(j1processes).to.gte(1); + expect(j2processes).to.equal(1); + expect(j3processes).to.equal(1); + + expect(unhandledRejections).to.have.length(0); + }).timeout(10500); + }); +}); diff --git a/test/fixtures/add-tests.ts b/test/fixtures/add-tests.ts new file mode 100644 index 0000000..a2b121f --- /dev/null +++ b/test/fixtures/add-tests.ts @@ -0,0 +1,76 @@ +/* eslint-disable unicorn/no-process-exit */ +export default { + none: (): void => {}, + daily: agenda => { + agenda.define('once a day test job', (job, done) => { + process.send!('ran'); + done(); + process.exit(0); + }); + + agenda.every('one day', 'once a day test job'); + }, + 'daily-array': agenda => { + agenda.define('daily test 1', (job, done) => { + process.send!('test1-ran'); + done(); + }); + + agenda.define('daily test 2', (job, done) => { + process.send!('test2-ran'); + done(); + }); + + agenda.every('one day', ['daily test 1', 'daily test 2']); + }, + 'define-future-job': agenda => { + const future = new Date(); + future.setDate(future.getDate() + 1); + + agenda.define('job in the future', (job, done) => { + process.send!('ran'); + done(); + process.exit(0); + }); + + agenda.schedule(future, 'job in the future'); + }, + 'define-past-due-job': agenda => { + const past = new Date(); + past.setDate(past.getDate() - 1); + + agenda.define('job in the past', (job, done) => { + process.send!('ran'); + done(); + process.exit(0); + }); + + agenda.schedule(past, 'job in the past'); + }, + 'schedule-array': agenda => { + const past = new Date(); + past.setDate(past.getDate() - 1); + + agenda.define('scheduled test 1', (job, done) => { + process.send!('test1-ran'); + done(); + }); + + agenda.define('scheduled test 2', (job, done) => { + process.send!('test2-ran'); + done(); + }); + + agenda.schedule(past, ['scheduled test 1', 'scheduled test 2']); + }, + now(agenda) { + agenda.define('now run this job', (job, done) => { + process.send!('ran'); + done(); + process.exit(0); + }); + + agenda.now('now run this job'); + } +}; +/* eslint-enable unicorn/no-process-exit */ diff --git a/test/fixtures/agenda-instance.ts b/test/fixtures/agenda-instance.ts new file mode 100644 index 0000000..d23fc19 --- /dev/null +++ b/test/fixtures/agenda-instance.ts @@ -0,0 +1,35 @@ +import { Agenda } from '../../src'; +import addTests from './add-tests'; + +const connStr = process.argv[2]; +const tests = process.argv.slice(3); + +const agenda = new Agenda( + { + db: { + address: connStr + }, + processEvery: 100 + }, + async () => { + tests.forEach(test => { + addTests[test](agenda); + }); + + await agenda.start(); + + // Ensure we can shut down the process from tests + process.on('message', msg => { + if (msg === 'exit') { + process.exit(0); + } + }); + + // Send default message of "notRan" after 400ms + setTimeout(() => { + process.send!('notRan'); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(0); + }, 400); + } +); diff --git a/test/fixtures/someJobDefinition.ts b/test/fixtures/someJobDefinition.ts new file mode 100644 index 0000000..0acd9c3 --- /dev/null +++ b/test/fixtures/someJobDefinition.ts @@ -0,0 +1,12 @@ +import { Agenda } from '../../src'; + +export default (agenda: Agenda, _definitionOnly = false) => { + agenda.define('some job', async job => { + console.log('HELLO from a sub worker'); + if (job.attrs.data?.failIt === 'error') { + throw new Error('intended error :-)'); + } else if (job.attrs.data?.failIt === 'die') { + process.exit(2); + } + }); +}; diff --git a/test/helpers/forkHelper.ts b/test/helpers/forkHelper.ts new file mode 100644 index 0000000..48f0c5f --- /dev/null +++ b/test/helpers/forkHelper.ts @@ -0,0 +1,63 @@ +import { Agenda } from '../../src'; + +process.on('message', message => { + if (message === 'cancel') { + process.exit(2); + } else { + console.log('got message', message); + } +}); + +(async () => { + /** do other required initializations */ + + // get process arguments (name, jobId and path to agenda definition file) + const [, , name, jobId, agendaDefinition] = process.argv; + + // set fancy process title + process.title = `${process.title} (sub worker: ${name}/${jobId})`; + + // initialize Agenda in "forkedWorker" mode + const agenda = new Agenda({ name: `subworker-${name}`, forkedWorker: true }); + // connect agenda (but do not start it) + await agenda.database(process.env.DB_CONNECTION!); + + if (!name || !jobId) { + throw new Error(`invalid parameters: ${JSON.stringify(process.argv)}`); + } + + // load job definition + /** in this case the file is for example ../some/path/definitions.js + with a content like: + export default (agenda: Agenda, definitionOnly = false) => { + agenda.define( + 'some job', + async (notification: { + attrs: { data: { dealId: string; orderId: TypeObjectId } }; + }) => { + // do something + } + ); + + if (!definitionOnly) { + // here you can create scheduled jobs or other things + } + }); + */ + if (agendaDefinition) { + const loadDefinition = await import(agendaDefinition); + (loadDefinition.default || loadDefinition)(agenda, true); + } + + // run this job now + await agenda.runForkedJob(jobId); + + // disconnect database and exit + process.exit(0); +})().catch(err => { + console.error('err', err); + if (process.send) { + process.send(JSON.stringify(err)); + } + process.exit(1); +}); diff --git a/test/helpers/mock-mongodb.ts b/test/helpers/mock-mongodb.ts new file mode 100644 index 0000000..2b8101b --- /dev/null +++ b/test/helpers/mock-mongodb.ts @@ -0,0 +1,28 @@ +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { MongoClient } from 'mongodb'; +import * as debug from 'debug'; + +const log = debug('agenda:mock-mongodb'); + +export interface IMockMongo { + disconnect: () => void; + mongo: MongoClient; + mongod: MongoMemoryServer; + uri: string; +} + +export async function mockMongo(): Promise { + const self: IMockMongo = {} as any; + self.mongod = await MongoMemoryServer.create(); + const uri = self.mongod.getUri(); + log('mongod started', uri); + self.mongo = await MongoClient.connect(uri); + self.disconnect = function () { + self.mongod.stop(); + log('mongod stopped'); + self.mongo.close(); + }; + self.uri = uri; + + return self; +} diff --git a/test/job.test.ts b/test/job.test.ts new file mode 100644 index 0000000..a680038 --- /dev/null +++ b/test/job.test.ts @@ -0,0 +1,1789 @@ +/* eslint-disable no-console */ +import * as path from 'path'; +import * as cp from 'child_process'; +import { expect } from 'chai'; +import * as assert from 'node:assert'; +import { DateTime } from 'luxon'; +import { Db } from 'mongodb'; + +import * as delay from 'delay'; +import * as sinon from 'sinon'; +import { fail } from 'assert'; +import { Job } from '../src/Job'; +import { Agenda } from '../src'; +import { mockMongo } from './helpers/mock-mongodb'; +import someJobDefinition from './fixtures/someJobDefinition'; + +// Create agenda instances +let agenda: Agenda; +// connection string to mongodb +let mongoCfg: string; +// mongo db connection db instance +let mongoDb: Db; + +const clearJobs = async () => { + if (mongoDb) { + await mongoDb.collection('agendaJobs').deleteMany({}); + } +}; + +// Slow timeouts for Travis +const jobTimeout = 500; +const jobType = 'do work'; +const jobProcessor = () => {}; + +describe('Job', () => { + beforeEach(async () => { + if (!mongoDb) { + const mockedMongo = await mockMongo(); + mongoCfg = mockedMongo.uri; + mongoDb = mockedMongo.mongo.db(); + } + + return new Promise(resolve => { + agenda = new Agenda( + { + mongo: mongoDb + }, + async () => { + await delay(50); + await clearJobs(); + agenda.define('someJob', jobProcessor); + agenda.define('send email', jobProcessor); + agenda.define('some job', jobProcessor); + agenda.define(jobType, jobProcessor); + return resolve(); + } + ); + }); + }); + + afterEach(async () => { + await delay(50); + await agenda.stop(); + await clearJobs(); + // await mongoClient.disconnect(); + // await jobs._db.close(); + }); + + describe('repeatAt', () => { + const job = new Job(agenda, { name: 'demo', type: 'normal' }); + it('sets the repeat at', () => { + job.repeatAt('3:30pm'); + expect(job.attrs.repeatAt).to.equal('3:30pm'); + }); + it('returns the job', () => { + expect(job.repeatAt('3:30pm')).to.equal(job); + }); + }); + + describe('toJSON', () => { + it('failedAt', () => { + let job = new Job(agenda, { + name: 'demo', + type: 'normal', + nextRunAt: null, + failedAt: null as any + }); + expect(job.toJson().failedAt).to.be.not.a('Date'); + + job = new Job(agenda, { + name: 'demo', + type: 'normal', + nextRunAt: null, + failedAt: new Date() + }); + expect(job.toJson().failedAt).to.be.a('Date'); + }); + }); + + describe('unique', () => { + const job = new Job(agenda, { name: 'demo', type: 'normal' }); + it('sets the unique property', () => { + job.unique({ 'data.type': 'active', 'data.userId': '123' }); + expect(JSON.stringify(job.attrs.unique)).to.equal( + JSON.stringify({ 'data.type': 'active', 'data.userId': '123' }) + ); + }); + it('returns the job', () => { + expect(job.unique({ 'data.type': 'active', 'data.userId': '123' })).to.equal(job); + }); + }); + + describe('repeatEvery', () => { + const job = new Job(agenda, { name: 'demo', type: 'normal' }); + it('sets the repeat interval', () => { + job.repeatEvery(5000); + expect(job.attrs.repeatInterval).to.equal(5000); + }); + it('returns the job', () => { + expect(job.repeatEvery('one second')).to.equal(job); + }); + it('sets the nextRunAt property with skipImmediate', () => { + const job2 = new Job(agenda, { name: 'demo', type: 'normal' }); + const now = new Date().valueOf(); + job2.repeatEvery('3 minutes', { skipImmediate: true }); + expect(job2.attrs.nextRunAt).to.be.within(new Date(now + 180000), new Date(now + 180002)); // Inclusive + }); + it('repeats from the existing nextRunAt property with skipImmediate', () => { + const job2 = new Job(agenda, { name: 'demo', type: 'normal' }); + const futureDate = new Date('3000-01-01T00:00:00'); + job2.attrs.nextRunAt = futureDate; + job2.repeatEvery('3 minutes', { skipImmediate: true }); + expect(job2.attrs.nextRunAt!.getTime()).to.equal(futureDate.getTime() + 180000); + }); + it('repeats from the existing scheduled date with skipImmediate', () => { + const futureDate = new Date('3000-01-01T00:00:00'); + const job2 = new Job(agenda, { name: 'demo', type: 'normal' }).schedule(futureDate); + job2.repeatEvery('3 minutes', { skipImmediate: true }); + expect(job2.attrs.nextRunAt!.getTime()).to.equal(futureDate.valueOf() + 180000); + }); + }); + + describe('schedule', () => { + let job; + beforeEach(() => { + job = new Job(agenda, { name: 'demo', type: 'normal' }); + }); + it('sets the next run time', () => { + job.schedule('in 5 minutes'); + expect(job.attrs.nextRunAt).to.be.an.instanceof(Date); + }); + it('sets the next run time Date object', () => { + const when = new Date(Date.now() + 1000 * 60 * 3); + job.schedule(when); + expect(job.attrs.nextRunAt).to.be.an.instanceof(Date); + expect(job.attrs.nextRunAt.getTime()).to.eql(when.getTime()); + }); + it('returns the job', () => { + expect(job.schedule('tomorrow at noon')).to.equal(job); + }); + it('understands ISODates on the 30th', () => { + // https://github.com/agenda/agenda/issues/807 + expect(job.schedule('2019-04-30T22:31:00.00Z').attrs.nextRunAt.getTime()).to.equal( + 1556663460000 + ); + }); + }); + + describe('priority', () => { + let job; + beforeEach(() => { + job = new Job(agenda, { name: 'demo', type: 'normal' }); + }); + it('sets the priority to a number', () => { + job.priority(10); + expect(job.attrs.priority).to.equal(10); + }); + it('returns the job', () => { + expect(job.priority(50)).to.equal(job); + }); + it('parses written priorities', () => { + job.priority('high'); + expect(job.attrs.priority).to.equal(10); + }); + }); + + describe('computeNextRunAt', () => { + let job: Job; + + beforeEach(() => { + job = new Job(agenda, { name: 'demo', type: 'normal' }); + }); + + it('returns the job', () => { + const jobProto = Object.getPrototypeOf(job); + expect(jobProto.computeNextRunAt.call(job)).to.equal(job); + }); + + it('sets to undefined if no repeat at', () => { + job.attrs.repeatAt = undefined; + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(job.attrs.nextRunAt).to.equal(null); + }); + + it('it understands repeatAt times', () => { + const d = new Date(); + d.setHours(23); + d.setMinutes(59); + d.setSeconds(0); + job.attrs.repeatAt = '11:59pm'; + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(job.attrs.nextRunAt?.getHours()).to.equal(d.getHours()); + expect(job.attrs.nextRunAt?.getMinutes()).to.equal(d.getMinutes()); + }); + + it('sets to undefined if no repeat interval', () => { + job.attrs.repeatInterval = undefined; + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(job.attrs.nextRunAt).to.equal(null); + }); + + it('it understands human intervals', () => { + const now = new Date(); + job.attrs.lastRunAt = now; + job.repeatEvery('2 minutes'); + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(job.attrs.nextRunAt?.getTime()).to.equal(now.valueOf() + 120000); + }); + + it('understands cron intervals', () => { + const now = new Date(); + now.setMinutes(1); + now.setMilliseconds(0); + now.setSeconds(0); + job.attrs.lastRunAt = now; + job.repeatEvery('*/2 * * * *'); + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(job.attrs.nextRunAt?.valueOf()).to.equal(now.valueOf() + 60000); + }); + + it('understands cron intervals with a timezone', () => { + const date = new Date('2015-01-01T06:01:00-00:00'); + job.attrs.lastRunAt = date; + job.repeatEvery('0 6 * * *', { + timezone: 'GMT' + }); + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(DateTime.fromJSDate(job.attrs.nextRunAt!).setZone('GMT').hour).to.equal(6); + expect(DateTime.fromJSDate(job.attrs.nextRunAt!).toJSDate().getDate()).to.equal( + DateTime.fromJSDate(job.attrs.lastRunAt!).plus({ days: 1 }).toJSDate().getDate() + ); + }); + + it('understands cron intervals with a vienna timezone with higher hours', () => { + const date = new Date('2015-01-01T06:01:00-00:00'); + job.attrs.lastRunAt = date; + job.repeatEvery('0 16 * * *', { + timezone: 'Europe/Vienna' + }); + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(DateTime.fromJSDate(job.attrs.nextRunAt!).setZone('GMT').hour).to.equal(15); + expect(DateTime.fromJSDate(job.attrs.nextRunAt!).toJSDate().getDate()).to.equal( + DateTime.fromJSDate(job.attrs.lastRunAt!).toJSDate().getDate() + ); + }); + + it('understands cron intervals with a timezone when last run is the same as the interval', () => { + const date = new Date('2015-01-01T06:00:00-00:00'); + job.attrs.lastRunAt = date; + job.repeatEvery('0 6 * * *', { + timezone: 'GMT' + }); + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(DateTime.fromJSDate(job.attrs.nextRunAt!).setZone('GMT').hour).to.equal(6); + expect(DateTime.fromJSDate(job.attrs.nextRunAt!).toJSDate().getDate()).to.equal( + DateTime.fromJSDate(job.attrs.lastRunAt!).plus({ days: 1 }).toJSDate().getDate() + ); + }); + + it('gives the correct nextDate when the lastRun is 1ms before the expected time', () => { + // (Issue #858): lastRunAt being 1ms before the nextRunAt makes cronTime return the same nextRunAt + const last = new Date(); + last.setSeconds(59); + last.setMilliseconds(999); + const next = new Date(last.valueOf() + 1); + const expectedDate = new Date(next.valueOf() + 60000); + job.attrs.lastRunAt = last; + job.attrs.nextRunAt = next; + job.repeatEvery('* * * * *', { + timezone: 'GMT' + }); + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + expect(job.attrs.nextRunAt.valueOf()).to.equal(expectedDate.valueOf()); + }); + + it('cron job with month starting at 1', async () => { + job.repeatEvery('0 0 * 1 *', { + timezone: 'GMT' + }); + if (job.attrs.nextRunAt) { + expect(job.attrs.nextRunAt.getMonth()).to.equal(0); + } else { + fail(); + } + }); + + it('repeating job with cron', async () => { + job.repeatEvery('0 0 * 1 *', { + timezone: 'GMT' + }); + expect(job.attrs.nextRunAt).to.not.eql(null); + }); + + describe('when repeat at time is invalid', () => { + beforeEach(() => { + job.attrs.repeatAt = 'foo'; + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + }); + + it('sets nextRunAt to null', () => { + expect(job.attrs.nextRunAt).to.equal(null); + }); + + it('fails the job', () => { + expect(job.attrs.failReason).to.equal( + 'failed to calculate repeatAt time due to invalid format' + ); + }); + }); + + describe('when repeat interval is invalid', () => { + beforeEach(() => { + job.attrs.repeatInterval = 'asd'; + const jobProto = Object.getPrototypeOf(job); + jobProto.computeNextRunAt.call(job); + }); + + it('sets nextRunAt to null', () => { + expect(job.attrs.nextRunAt).to.equal(null); + }); + + it('fails the job', () => { + expect(job.attrs.failReason).to.equal( + 'failed to calculate nextRunAt due to invalid repeat interval (asd): Error: Validation error, cannot resolve alias "asd"' + ); + }); + }); + }); + + describe('remove', () => { + it('removes the job', async () => { + const job = new Job(agenda, { + name: 'removed job', + type: 'normal' + }); + await job.save(); + const resultSaved = await mongoDb + .collection('agendaJobs') + .find({ + _id: job.attrs._id + }) + .toArray(); + + expect(resultSaved).to.have.length(1); + await job.remove(); + + const resultDeleted = await mongoDb + .collection('agendaJobs') + .find({ + _id: job.attrs._id + }) + .toArray(); + + expect(resultDeleted).to.have.length(0); + }); + }); + + describe('run', () => { + beforeEach(async () => { + agenda.define('testRun', (_job, done) => { + setTimeout(() => { + done(); + }, 100); + }); + }); + + it('updates lastRunAt', async () => { + const job = new Job(agenda, { name: 'testRun', type: 'normal' }); + await job.save(); + const now = new Date(); + await delay(5); + await job.run(); + + expect(job.attrs.lastRunAt?.valueOf()).to.greaterThan(now.valueOf()); + }); + + it('fails if job is undefined', async () => { + const job = new Job(agenda, { name: 'not defined', type: 'normal' }); + await job.save(); + + await job.run().catch(error => { + expect(error.message).to.equal('Undefined job'); + }); + expect(job.attrs.failedAt).to.not.be.undefined; + expect(job.attrs.failReason).to.equal('Undefined job'); + }); + + it('updates nextRunAt', async () => { + const job = new Job(agenda, { name: 'testRun', type: 'normal' }); + await job.save(); + + const now = new Date(); + job.repeatEvery('10 minutes'); + await delay(5); + await job.run(); + expect(job.attrs.nextRunAt?.valueOf()).to.greaterThan(now.valueOf() + 59999); + }); + + it('handles errors', async () => { + const job = new Job(agenda, { name: 'failBoat', type: 'normal' }); + await job.save(); + + agenda.define('failBoat', () => { + throw new Error('Zomg fail'); + }); + await job.run(); + expect(job.attrs.failReason).to.equal('Zomg fail'); + }); + + it('handles errors with q promises', async () => { + const job = new Job(agenda, { name: 'failBoat2', type: 'normal' }); + await job.save(); + + agenda.define('failBoat2', async (_job, cb) => { + try { + throw new Error('Zomg fail'); + } catch (err: any) { + cb(err); + } + }); + await job.run(); + expect(job.attrs.failReason).to.not.be.undefined; + }); + + it('allows async functions', async () => { + const job = new Job(agenda, { name: 'async', type: 'normal' }); + await job.save(); + + const successSpy = sinon.stub(); + let finished = false; + + agenda.once('success:async', successSpy); + + agenda.define('async', async () => { + await delay(5); + finished = true; + }); + + expect(finished).to.equal(false); + await job.run(); + expect(successSpy.callCount).to.equal(1); + expect(finished).to.equal(true); + }); + + it('handles errors from async functions', async () => { + const job = new Job(agenda, { name: 'asyncFail', type: 'normal' }); + await job.save(); + + const failSpy = sinon.stub(); + const err = new Error('failure'); + + agenda.once('fail:asyncFail', failSpy); + + agenda.define('asyncFail', async () => { + await delay(5); + throw err; + }); + + await job.run(); + expect(failSpy.callCount).to.equal(1); + expect(failSpy.calledWith(err)).to.equal(true); + }); + + it('waits for the callback to be called even if the function is async', async () => { + const job = new Job(agenda, { name: 'asyncCb', type: 'normal' }); + await job.save(); + + const successSpy = sinon.stub(); + let finishedCb = false; + + agenda.once('success:asyncCb', successSpy); + + agenda.define('asyncCb', async (_job, cb) => { + (async () => { + await delay(5); + finishedCb = true; + cb(); + })(); + }); + + await job.run(); + expect(finishedCb).to.equal(true); + expect(successSpy.callCount).to.equal(1); + }); + + it("uses the callback error if the function is async and didn't reject", async () => { + const job = new Job(agenda, { name: 'asyncCbError', type: 'normal' }); + await job.save(); + + const failSpy = sinon.stub(); + const err = new Error('failure'); + + agenda.once('fail:asyncCbError', failSpy); + + agenda.define('asyncCbError', async (_job, cb) => { + (async () => { + await delay(5); + cb(err); + })(); + }); + + await job.run(); + expect(failSpy.callCount).to.equal(1); + expect(failSpy.calledWith(err)).to.equal(true); + }); + + it('favors the async function error over the callback error if it comes first', async () => { + const job = new Job(agenda, { name: 'asyncCbTwoError', type: 'normal' }); + await job.save(); + + const failSpy = sinon.stub(); + const fnErr = new Error('functionFailure'); + const cbErr = new Error('callbackFailure'); + + agenda.on('fail:asyncCbTwoError', failSpy); + + agenda.define('asyncCbTwoError', async (_job, cb) => { + (async () => { + await delay(5); + cb(cbErr); + })(); + + throw fnErr; + }); + + await job.run(); + expect(failSpy.callCount).to.equal(1); + expect(failSpy.calledWith(fnErr)).to.equal(true); + expect(failSpy.calledWith(cbErr)).to.equal(false); + }); + + it('favors the callback error over the async function error if it comes first', async () => { + const job = new Job(agenda, { name: 'asyncCbTwoErrorCb', type: 'normal' }); + await job.save(); + + const failSpy = sinon.stub(); + const fnErr = new Error('functionFailure'); + const cbErr = new Error('callbackFailure'); + + agenda.on('fail:asyncCbTwoErrorCb', failSpy); + + agenda.define('asyncCbTwoErrorCb', async (_job, cb) => { + cb(cbErr); + await delay(5); + throw fnErr; + }); + + await job.run(); + expect(failSpy.callCount).to.equal(1); + expect(failSpy.calledWith(cbErr)).to.equal(true); + expect(failSpy.calledWith(fnErr)).to.equal(false); + }); + + it("doesn't allow a stale job to be saved", async () => { + const job = new Job(agenda, { name: 'failBoat3', type: 'normal' }); + await job.save(); + + agenda.define('failBoat3', async (_job, cb) => { + // Explicitly find the job again, + // so we have a new job object + const jobs = await agenda.jobs({ name: 'failBoat3' }); + expect(jobs).to.have.length(1); + await jobs[0].remove(); + cb(); + }); + + await job.run(); + + // Expect the deleted job to not exist in the database + const deletedJob = await agenda.jobs({ name: 'failBoat3' }); + expect(deletedJob).to.have.length(0); + }); + }); + + describe('touch', () => { + it('extends the lock lifetime', async () => { + const lockedAt = new Date(); + const job = new Job(agenda, { name: 'some job', type: 'normal', lockedAt }); + await job.save(); + await delay(2); + await job.touch(); + expect(job.attrs.lockedAt).to.greaterThan(lockedAt); + }); + }); + + describe('fail', () => { + const job = new Job(agenda, { name: 'demo', type: 'normal' }); + it('takes a string', () => { + job.fail('test'); + expect(job.attrs.failReason).to.equal('test'); + }); + it('takes an error object', () => { + job.fail(new Error('test')); + expect(job.attrs.failReason).to.equal('test'); + }); + it('sets the failedAt time', () => { + job.fail('test'); + expect(job.attrs.failedAt).to.be.an.instanceof(Date); + }); + it('sets the failedAt time equal to lastFinishedAt time', () => { + job.fail('test'); + expect(job.attrs.failedAt).to.equal(job.attrs.lastFinishedAt); + }); + }); + + describe('enable', () => { + it('sets disabled to false on the job', () => { + const job = new Job(agenda, { name: 'test', type: 'normal', disabled: true }); + job.enable(); + expect(job.attrs.disabled).to.equal(false); + }); + + it('returns the job', () => { + const job = new Job(agenda, { name: 'test', type: 'normal', disabled: true }); + expect(job.enable()).to.equal(job); + }); + }); + + describe('disable', () => { + it('sets disabled to true on the job', () => { + const job = new Job(agenda, { name: 'demo', type: 'normal' }); + job.disable(); + expect(job.attrs.disabled).to.be.true; + }); + it('returns the job', () => { + const job = new Job(agenda, { name: 'demo', type: 'normal' }); + expect(job.disable()).to.equal(job); + }); + }); + + describe('save', () => { + /** this is undocumented, and therefore we remvoe it + it('calls saveJob on the agenda', done => { + const oldSaveJob = agenda.saveJob; + agenda.saveJob = () => { + agenda.saveJob = oldSaveJob; + done(); + }; + + const job = agenda.create('some job', { + wee: 1 + }); + job.save(); + }); */ + + it('doesnt save the job if its been removed', async () => { + const job = agenda.create('another job'); + // Save, then remove, then try and save again. + // The second save should fail. + const j = await job.save(); + await j.remove(); + await j.save(); + + const jobs = await agenda.jobs({ name: 'another job' }); + expect(jobs).to.have.length(0); + }); + + it('returns the job', async () => { + const job = agenda.create('some job', { + wee: 1 + }); + expect(await job.save()).to.equal(job); + }); + }); + + describe('start/stop', () => { + it('starts/stops the job queue', async () => { + const processed = new Promise(resolve => { + agenda.define('jobQueueTest', async _job => { + resolve('processed'); + }); + }); + await agenda.every('1 second', 'jobQueueTest'); + agenda.processEvery('1 second'); + await agenda.start(); + + expect( + await Promise.race([ + processed, + new Promise(resolve => { + setTimeout(() => resolve(`not processed`), 1100); + }) + ]) + ).to.eq('processed'); + + await agenda.stop(); + const processedStopped = new Promise(resolve => { + agenda.define('jobQueueTest', async _job => { + resolve(); + }); + }); + + expect( + await Promise.race([ + processedStopped, + new Promise(resolve => { + setTimeout(() => resolve(`not processed`), 1100); + }) + ]) + ).to.eq('not processed'); + }); + + it('does not run disabled jobs', async () => { + let ran = false; + agenda.define('disabledJob', () => { + ran = true; + }); + + const job = await agenda.create('disabledJob').disable().schedule('now'); + await job.save(); + await agenda.start(); + await delay(jobTimeout); + + expect(ran).to.equal(false); + + await agenda.stop(); + }); + + it('does not throw an error trying to process undefined jobs', async () => { + await agenda.start(); + const job = agenda.create('jobDefinedOnAnotherServer').schedule('now'); + + await job.save(); + + await delay(jobTimeout); + await agenda.stop(); + }); + + it('clears locks on stop', async () => { + agenda.define('longRunningJob', (_job, _cb) => { + // eslint-disable-line no-unused-vars + // Job never finishes + }); + agenda.every('10 seconds', 'longRunningJob'); + agenda.processEvery('1 second'); + + await agenda.start(); + await delay(jobTimeout); + const jobStarted = await agenda.db.getJobs({ name: 'longRunningJob' }); + expect(jobStarted[0].lockedAt).to.not.equal(null); + await agenda.stop(); + const job = await agenda.db.getJobs({ name: 'longRunningJob' }); + expect(job[0].lockedAt).to.equal(undefined); + }); + + describe('events', () => { + beforeEach(() => { + agenda.define('jobQueueTest', (_job, cb) => { + cb(); + }); + agenda.define('failBoat', () => { + throw new Error('Zomg fail'); + }); + }); + + it('emits start event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'jobQueueTest', type: 'normal' }); + await job.save(); + agenda.once('start', spy); + + await job.run(); + expect(spy.called).to.be.true; + expect(spy.calledWithExactly(job)).to.be.true; + }); + + it('emits start:job name event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'jobQueueTest', type: 'normal' }); + await job.save(); + agenda.once('start:jobQueueTest', spy); + + await job.run(); + expect(spy.called).to.be.true; + expect(spy.calledWithExactly(job)).to.be.true; + }); + + it('emits complete event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'jobQueueTest', type: 'normal' }); + await job.save(); + agenda.once('complete', spy); + + await job.run(); + expect(spy.called).to.be.true; + expect(spy.calledWithExactly(job)).to.be.true; + }); + + it('emits complete:job name event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'jobQueueTest', type: 'normal' }); + await job.save(); + agenda.once('complete:jobQueueTest', spy); + + await job.run(); + expect(spy.called).to.be.true; + expect(spy.calledWithExactly(job)).to.be.true; + }); + + it('emits success event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'jobQueueTest', type: 'normal' }); + await job.save(); + agenda.once('success', spy); + + await job.run(); + expect(spy.called).to.be.true; + expect(spy.calledWithExactly(job)).to.be.true; + }); + + it('emits success:job name event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'jobQueueTest', type: 'normal' }); + await job.save(); + agenda.once('success:jobQueueTest', spy); + + await job.run(); + expect(spy.called).to.be.true; + expect(spy.calledWithExactly(job)).to.be.true; + }); + + it('emits fail event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'failBoat', type: 'normal' }); + await job.save(); + agenda.once('fail', spy); + + await job.run().catch(error => { + expect(error.message).to.equal('Zomg fail'); + }); + + expect(spy.called).to.be.true; + + const err = spy.args[0][0]; + expect(err.message).to.equal('Zomg fail'); + expect(job.attrs.failCount).to.equal(1); + expect(job.attrs.failedAt!.valueOf()).not.to.be.below(job.attrs.lastFinishedAt!.valueOf()); + }); + + it('emits fail:job name event', async () => { + const spy = sinon.spy(); + const job = new Job(agenda, { name: 'failBoat', type: 'normal' }); + await job.save(); + agenda.once('fail:failBoat', spy); + + await job.run().catch(error => { + expect(error.message).to.equal('Zomg fail'); + }); + + expect(spy.called).to.be.true; + + const err = spy.args[0][0]; + expect(err.message).to.equal('Zomg fail'); + expect(job.attrs.failCount).to.equal(1); + expect(job.attrs.failedAt!.valueOf()).to.not.be.below(job.attrs.lastFinishedAt!.valueOf()); + }); + }); + }); + + describe('job lock', () => { + it('runs a recurring job after a lock has expired', async () => { + const processorPromise = new Promise(resolve => { + let startCounter = 0; + agenda.define( + 'lock job', + async () => { + startCounter++; + + if (startCounter !== 1) { + await agenda.stop(); + resolve(startCounter); + } + }, + { + lockLifetime: 50 + } + ); + }); + + expect(agenda.definitions['lock job'].lockLifetime).to.equal(50); + + agenda.defaultConcurrency(100); + agenda.processEvery(10); + agenda.every('0.02 seconds', 'lock job'); + await agenda.stop(); + await agenda.start(); + expect(await processorPromise).to.equal(2); + }); + + it('runs a one-time job after its lock expires', async () => { + const processorPromise = new Promise(resolve => { + let runCount = 0; + + agenda.define( + 'lock job', + async _job => { + runCount++; + if (runCount === 1) { + // this should time out + await new Promise(longResolve => { + setTimeout(longResolve, 1000); + }); + } else { + await new Promise(longResolve => { + setTimeout(longResolve, 10); + }); + resolve(runCount); + } + }, + { + lockLifetime: 50, + concurrency: 1 + } + ); + }); + + let errorHasBeenThrown; + agenda.on('error', err => { + errorHasBeenThrown = err; + }); + agenda.processEvery(25); + await agenda.start(); + agenda.now('lock job', { + i: 1 + }); + expect(await processorPromise).to.equal(2); + expect(errorHasBeenThrown?.message).to.includes("execution of 'lock job' canceled"); + }); + + it('does not process locked jobs', async () => { + const history: any[] = []; + + agenda.define( + 'lock job', + (job, cb) => { + history.push(job.attrs.data.i); + + setTimeout(() => { + cb(); + }, 150); + }, + { + lockLifetime: 300 + } + ); + + agenda.processEvery(100); + await agenda.start(); + + await Promise.all([ + agenda.now('lock job', { i: 1 }), + agenda.now('lock job', { i: 2 }), + agenda.now('lock job', { i: 3 }) + ]); + + await delay(500); + expect(history).to.have.length(3); + expect(history).to.contain(1); + expect(history).to.contain(2); + expect(history).to.contain(3); + }); + + it('does not on-the-fly lock more than agenda._lockLimit jobs', async () => { + agenda.lockLimit(1); + + agenda.define('lock job', (_job, _cb) => { + /* this job nevers finishes */ + }); // eslint-disable-line no-unused-vars + + await agenda.start(); + + await Promise.all([agenda.now('lock job', { i: 1 }), agenda.now('lock job', { i: 2 })]); + + // give it some time to get picked up + await delay(200); + + expect((await agenda.getRunningStats()).lockedJobs).to.equal(1); + }); + + it('does not on-the-fly lock more mixed jobs than agenda._lockLimit jobs', async () => { + agenda.lockLimit(1); + + agenda.define('lock job', (_job, _cb) => {}); // eslint-disable-line no-unused-vars + agenda.define('lock job2', (_job, _cb) => {}); // eslint-disable-line no-unused-vars + agenda.define('lock job3', (_job, _cb) => {}); // eslint-disable-line no-unused-vars + agenda.define('lock job4', (_job, _cb) => {}); // eslint-disable-line no-unused-vars + agenda.define('lock job5', (_job, _cb) => {}); // eslint-disable-line no-unused-vars + + await agenda.start(); + + await Promise.all([ + agenda.now('lock job', { i: 1 }), + agenda.now('lock job5', { i: 2 }), + agenda.now('lock job4', { i: 3 }), + agenda.now('lock job3', { i: 4 }), + agenda.now('lock job2', { i: 5 }) + ]); + + await delay(500); + expect((await agenda.getRunningStats()).lockedJobs).to.equal(1); + await agenda.stop(); + }); + + it('does not on-the-fly lock more than definition.lockLimit jobs', async () => { + agenda.define('lock job', (_job, _cb) => {}, { lockLimit: 1 }); // eslint-disable-line no-unused-vars + + await agenda.start(); + + await Promise.all([agenda.now('lock job', { i: 1 }), agenda.now('lock job', { i: 2 })]); + + await delay(500); + expect((await agenda.getRunningStats()).lockedJobs).to.equal(1); + }); + + it('does not lock more than agenda._lockLimit jobs during processing interval', async () => { + agenda.lockLimit(1); + agenda.processEvery(200); + + agenda.define('lock job', (_job, _cb) => {}); // eslint-disable-line no-unused-vars + + await agenda.start(); + + const when = DateTime.local().plus({ milliseconds: 300 }).toJSDate(); + + await Promise.all([ + agenda.schedule(when, 'lock job', { i: 1 }), + agenda.schedule(when, 'lock job', { i: 2 }) + ]); + + await delay(500); + expect((await agenda.getRunningStats()).lockedJobs).to.equal(1); + }); + + it('does not lock more than definition.lockLimit jobs during processing interval', async () => { + agenda.processEvery(200); + + agenda.define('lock job', (_job, _cb) => {}, { lockLimit: 1 }); // eslint-disable-line no-unused-vars + + await agenda.start(); + + const when = DateTime.local().plus({ milliseconds: 300 }).toJSDate(); + + await Promise.all([ + agenda.schedule(when, 'lock job', { i: 1 }), + agenda.schedule(when, 'lock job', { i: 2 }) + ]); + + await delay(500); + expect((await agenda.getRunningStats()).lockedJobs).to.equal(1); + await agenda.stop(); + }); + }); + + describe('job concurrency', () => { + it('should not block a job for concurrency of another job', async () => { + agenda.processEvery(50); + + const processed: number[] = []; + const now = Date.now(); + + agenda.define( + 'blocking', + (job, cb) => { + processed.push(job.attrs.data.i); + setTimeout(cb, 400); + }, + { + concurrency: 1 + } + ); + + const checkResultsPromise = new Promise(resolve => { + agenda.define( + 'non-blocking', + job => { + processed.push(job.attrs.data.i); + resolve(processed); + }, + { + // Lower priority to keep it at the back in the queue + priority: 'lowest' + } + ); + }); + + let finished = false; + agenda.on('complete', () => { + if (!finished && processed.length === 3) { + finished = true; + } + }); + + agenda.start(); + + await Promise.all([ + agenda.schedule(new Date(now + 100), 'blocking', { i: 1 }), + agenda.schedule(new Date(now + 101), 'blocking', { i: 2 }), + agenda.schedule(new Date(now + 102), 'non-blocking', { i: 3 }) + ]); + + try { + const results: number[] = await Promise.race([ + checkResultsPromise, + // eslint-disable-next-line prefer-promise-reject-errors + new Promise((_, reject) => { + setTimeout(() => { + reject(`not processed`); + }, 2000); + }) + ]); + expect(results).not.to.contain(2); + } catch (err) { + console.log('stats', err, JSON.stringify(await agenda.getRunningStats(), undefined, 3)); + throw err; + } + }); + + it('should run jobs as first in first out (FIFO)', async () => { + agenda.processEvery(100); + agenda.define('fifo', (_job, cb) => cb(), { concurrency: 1 }); + + const checkResultsPromise = new Promise(resolve => { + const results: number[] = []; + + agenda.on('start:fifo', job => { + results.push(new Date(job.attrs.nextRunAt!).getTime()); + if (results.length !== 3) { + return; + } + + resolve(results); + }); + }); + + await agenda.start(); + + await agenda.now('fifo'); + await delay(50); + await agenda.now('fifo'); + await delay(50); + await agenda.now('fifo'); + await delay(50); + try { + const results: number[] = await Promise.race([ + checkResultsPromise, + // eslint-disable-next-line prefer-promise-reject-errors + new Promise((_, reject) => { + setTimeout(() => { + reject(`not processed`); + }, 2000); + }) + ]); + expect(results.join('')).to.eql(results.sort().join('')); + } catch (err) { + console.log('stats', err, JSON.stringify(await agenda.getRunningStats(), undefined, 3)); + throw err; + } + }); + + it('should run jobs as first in first out (FIFO) with respect to priority', async () => { + const now = Date.now(); + + agenda.define('fifo-priority', (_job, cb) => setTimeout(cb, 100), { concurrency: 1 }); + + const checkResultsPromise = new Promise(resolve => { + const times: number[] = []; + const priorities: number[] = []; + + agenda.on('start:fifo-priority', job => { + priorities.push(job.attrs.priority); + times.push(new Date(job.attrs.lastRunAt!).getTime()); + if (priorities.length !== 3 || times.length !== 3) { + return; + } + + resolve({ times, priorities }); + }); + }); + + await Promise.all([ + agenda.create('fifo-priority', { i: 1 }).schedule(new Date(now)).priority('high').save(), + agenda + .create('fifo-priority', { i: 2 }) + .schedule(new Date(now + 100)) + .priority('low') + .save(), + agenda + .create('fifo-priority', { i: 3 }) + .schedule(new Date(now + 100)) + .priority('high') + .save() + ]); + await agenda.start(); + try { + const { times, priorities } = await Promise.race([ + checkResultsPromise, + // eslint-disable-next-line prefer-promise-reject-errors + new Promise((_, reject) => { + setTimeout(() => { + reject(`not processed`); + }, 2000); + }) + ]); + + expect(times.join('')).to.eql(times.sort().join('')); + expect(priorities).to.eql([10, 10, -10]); + } catch (err) { + console.log('stats', err, JSON.stringify(await agenda.getRunningStats(), undefined, 3)); + throw err; + } + }); + + it('should run higher priority jobs first', async () => { + // Inspired by tests added by @lushc here: + // + const now = new Date(); + + agenda.define('priority', (_job, cb) => setTimeout(cb, 10), { concurrency: 1 }); + + const checkResultsPromise = new Promise(resolve => { + const results: number[] = []; + + agenda.on('start:priority', job => { + results.push(job.attrs.priority); + if (results.length !== 3) { + return; + } + + resolve(results); + }); + }); + + await Promise.all([ + agenda.create('priority').schedule(now).save(), + agenda.create('priority').schedule(now).priority('low').save(), + agenda.create('priority').schedule(now).priority('high').save() + ]); + await agenda.start(); + try { + const results = await Promise.race([ + checkResultsPromise, + // eslint-disable-next-line prefer-promise-reject-errors + new Promise((_, reject) => { + setTimeout(() => { + reject(`not processed`); + }, 2000); + }) + ]); + expect(results).to.eql([10, 0, -10]); + } catch (err) { + console.log('stats', JSON.stringify(await agenda.getRunningStats(), undefined, 3)); + throw err; + } + }); + + it('should support custom sort option', () => { + const sort = { foo: 1 } as const; + const agendaSort = new Agenda({ sort }); + expect(agendaSort.attrs.sort).to.eql(sort); + }); + }); + + describe('every running', () => { + beforeEach(async () => { + agenda.defaultConcurrency(1); + agenda.processEvery(5); + + await agenda.stop(); + }); + + it('should run the same job multiple times', async () => { + let counter = 0; + + agenda.define('everyRunTest1', (_job, cb) => { + if (counter < 2) { + counter++; + } + + cb(); + }); + + await agenda.every(10, 'everyRunTest1'); + + await agenda.start(); + + await agenda.jobs({ name: 'everyRunTest1' }); + await delay(jobTimeout); + expect(counter).to.equal(2); + + await agenda.stop(); + }); + + it('should reuse the same job on multiple runs', async () => { + let counter = 0; + + agenda.define('everyRunTest2', (_job, cb) => { + if (counter < 2) { + counter++; + } + + cb(); + }); + await agenda.every(10, 'everyRunTest2'); + + await agenda.start(); + + await delay(jobTimeout); + const result = await agenda.jobs({ name: 'everyRunTest2' }); + + expect(result).to.have.length(1); + await agenda.stop(); + }); + }); + + describe('Integration Tests', () => { + describe('.every()', () => { + it('Should not rerun completed jobs after restart', done => { + let i = 0; + + const serviceError = function (e) { + done(e); + }; + + const receiveMessage = function (msg) { + if (msg === 'ran') { + expect(i).to.equal(0); + i += 1; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + startService(); + } else if (msg === 'notRan') { + expect(i).to.equal(1); + done(); + } else { + done(new Error('Unexpected response returned!')); + } + }; + + const startService = () => { + const serverPath = path.join(__dirname, 'fixtures', 'agenda-instance.ts'); + const n = cp.fork(serverPath, [mongoCfg, 'daily'], { + execArgv: ['-r', 'ts-node/register'] + }); + + n.on('message', receiveMessage); + n.on('error', serviceError); + }; + + startService(); + }); + + it('Should properly run jobs when defined via an array', done => { + const serverPath = path.join(__dirname, 'fixtures', 'agenda-instance.ts'); + const n = cp.fork(serverPath, [mongoCfg, 'daily-array'], { + execArgv: ['-r', 'ts-node/register'] + }); + + let ran1 = false; + let ran2 = false; + let doneCalled = false; + + const serviceError = function (e) { + done(e); + }; + + const receiveMessage = function (msg) { + if (msg === 'test1-ran') { + ran1 = true; + if (ran1 && ran2 && !doneCalled) { + doneCalled = true; + done(); + n.send('exit'); + } + } else if (msg === 'test2-ran') { + ran2 = true; + if (ran1 && ran2 && !doneCalled) { + doneCalled = true; + done(); + n.send('exit'); + } + } else if (!doneCalled) { + done(new Error('Jobs did not run!')); + } + }; + + n.on('message', receiveMessage); + n.on('error', serviceError); + }); + + it('should not run if job is disabled', async () => { + let counter = 0; + + agenda.define('everyDisabledTest', (_job, cb) => { + counter++; + cb(); + }); + + const job = await agenda.every(10, 'everyDisabledTest'); + + job.disable(); + + await job.save(); + await agenda.start(); + + await delay(jobTimeout); + await agenda.jobs({ name: 'everyDisabledTest' }); + expect(counter).to.equal(0); + await agenda.stop(); + }); + }); + + describe('schedule()', () => { + it('Should not run jobs scheduled in the future', done => { + let i = 0; + + const serviceError = function (e) { + done(e); + }; + + const receiveMessage = function (msg) { + if (msg === 'notRan') { + if (i < 5) { + done(); + return; + } + + i += 1; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + startService(); + } else { + done(new Error('Job scheduled in future was ran!')); + } + }; + + const startService = () => { + const serverPath = path.join(__dirname, 'fixtures', 'agenda-instance.ts'); + const n = cp.fork(serverPath, [mongoCfg, 'define-future-job'], { + execArgv: ['-r', 'ts-node/register'] + }); + + n.on('message', receiveMessage); + n.on('error', serviceError); + }; + + startService(); + }); + + it('Should run past due jobs when process starts', done => { + const serviceError = function (e) { + done(e); + }; + + const receiveMessage = function (msg) { + if (msg === 'ran') { + done(); + } else { + done(new Error('Past due job did not run!')); + } + }; + + const startService = () => { + const serverPath = path.join(__dirname, 'fixtures', 'agenda-instance.ts'); + const n = cp.fork(serverPath, [mongoCfg, 'define-past-due-job'], { + execArgv: ['-r', 'ts-node/register'] + }); + + n.on('message', receiveMessage); + n.on('error', serviceError); + }; + + startService(); + }); + + it('Should schedule using array of names', done => { + const serverPath = path.join(__dirname, 'fixtures', 'agenda-instance.ts'); + const n = cp.fork(serverPath, [mongoCfg, 'schedule-array'], { + execArgv: ['-r', 'ts-node/register'] + }); + + let ran1 = false; + let ran2 = false; + let doneCalled = false; + + const serviceError = err => { + done(err); + }; + + const receiveMessage = msg => { + if (msg === 'test1-ran') { + ran1 = true; + if (ran1 && ran2 && !doneCalled) { + doneCalled = true; + done(); + n.send('exit'); + } + } else if (msg === 'test2-ran') { + ran2 = true; + if (ran1 && ran2 && !doneCalled) { + doneCalled = true; + done(); + n.send('exit'); + } + } else if (!doneCalled) { + done(new Error('Jobs did not run!')); + } + }; + + n.on('message', receiveMessage); + n.on('error', serviceError); + }); + }); + + describe('now()', () => { + it('Should immediately run the job', done => { + const serviceError = function (e) { + done(e); + }; + + const receiveMessage = function (msg) { + if (msg === 'ran') { + return done(); + } + + return done(new Error('Job did not immediately run!')); + }; + + const serverPath = path.join(__dirname, 'fixtures', 'agenda-instance.ts'); + const n = cp.fork(serverPath, [mongoCfg, 'now'], { execArgv: ['-r', 'ts-node/register'] }); + + n.on('message', receiveMessage); + n.on('error', serviceError); + }); + }); + + describe('General Integration', () => { + it('Should not run a job that has already been run', async () => { + const runCount = {}; + + agenda.define('test-job', (job, cb) => { + const id = job.attrs._id!.toString(); + + runCount[id] = runCount[id] ? runCount[id] + 1 : 1; + cb(); + }); + + agenda.processEvery(100); + await agenda.start(); + + await Promise.all([...new Array(10)].map(() => agenda.now('test-job'))); + + await delay(jobTimeout); + const ids = Object.keys(runCount); + expect(ids).to.have.length(10); + Object.keys(runCount).forEach(id => { + expect(runCount[id]).to.equal(1); + }); + }); + }); + }); + + it('checks database for running job on "client"', async () => { + agenda.define('test', async () => { + await new Promise(resolve => { + setTimeout(resolve, 30000); + }); + }); + + const job = await agenda.now('test'); + await agenda.start(); + + await new Promise(resolve => { + agenda.on('start:test', resolve); + }); + + expect(await job.isRunning()).to.be.equal(true); + }); + + it('should not run job if is has been removed', async () => { + let executed = false; + agenda.define('test', async () => { + executed = true; + }); + + const job = new Job(agenda, { + name: 'test', + type: 'normal' + }); + job.schedule('in 1 second'); + await job.save(); + + await agenda.start(); + + let jobStarted; + let retried = 0; + // wait till it's locked (Picked up by the event processor) + do { + jobStarted = await agenda.db.getJobs({ name: 'test' }); + if (!jobStarted[0].lockedAt) { + delay(100); + } + retried++; + } while (!jobStarted[0].lockedAt || retried > 10); + + expect(jobStarted[0].lockedAt).to.exist; // .equal(null); + + await job.remove(); + + let error; + const completed = new Promise(resolve => { + agenda.on('error', err => { + error = err; + resolve(); + }); + }); + + await Promise.race([ + new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 1000); + }), + completed + ]); + + expect(executed).to.be.equal(false); + assert.ok(typeof error !== 'undefined'); + expect(error.message).to.includes('(name: test) cannot be updated in the database'); + }); + + describe('job fork mode', () => { + it('runs a job in fork mode', async () => { + const agendaFork = new Agenda({ + mongo: mongoDb, + forkHelper: { + path: './test/helpers/forkHelper.ts', + options: { + env: { DB_CONNECTION: mongoCfg }, + execArgv: ['-r', 'ts-node/register'] + } + } + }); + + expect(agendaFork.forkHelper?.path).to.be.eq('./test/helpers/forkHelper.ts'); + + const job = agendaFork.create('some job'); + job.forkMode(true); + job.schedule('now'); + await job.save(); + + const jobData = await agenda.db.getJobById(job.attrs._id as any); + + if (!jobData) { + throw new Error('job not found'); + } + + expect(jobData.fork).to.be.eq(true); + + // initialize job definition (keep in a seperate file to have a easier fork mode implementation) + someJobDefinition(agendaFork); + + await agendaFork.start(); + + do { + // console.log('.'); + await delay(50); + } while (await job.isRunning()); + + const jobDataFinished = await agenda.db.getJobById(job.attrs._id as any); + expect(jobDataFinished?.lastFinishedAt).to.not.be.eq(undefined); + expect(jobDataFinished?.failReason).to.be.eq(null); + expect(jobDataFinished?.failCount).to.be.eq(null); + }); + + it('runs a job in fork mode, but let it fail', async () => { + const agendaFork = new Agenda({ + mongo: mongoDb, + forkHelper: { + path: './test/helpers/forkHelper.ts', + options: { + env: { DB_CONNECTION: mongoCfg }, + execArgv: ['-r', 'ts-node/register'] + } + } + }); + + expect(agendaFork.forkHelper?.path).to.be.eq('./test/helpers/forkHelper.ts'); + + const job = agendaFork.create('some job', { failIt: 'error' }); + job.forkMode(true); + job.schedule('now'); + await job.save(); + + const jobData = await agenda.db.getJobById(job.attrs._id as any); + + if (!jobData) { + throw new Error('job not found'); + } + + expect(jobData.fork).to.be.eq(true); + + // initialize job definition (keep in a seperate file to have a easier fork mode implementation) + someJobDefinition(agendaFork); + + await agendaFork.start(); + + do { + // console.log('.'); + await delay(50); + } while (await job.isRunning()); + + const jobDataFinished = await agenda.db.getJobById(job.attrs._id as any); + expect(jobDataFinished?.lastFinishedAt).to.not.be.eq(undefined); + expect(jobDataFinished?.failReason).to.not.be.eq(null); + expect(jobDataFinished?.failCount).to.be.eq(1); + }); + + it('runs a job in fork mode, but let it die', async () => { + const agendaFork = new Agenda({ + mongo: mongoDb, + forkHelper: { + path: './test/helpers/forkHelper.ts', + options: { + env: { DB_CONNECTION: mongoCfg }, + execArgv: ['-r', 'ts-node/register'] + } + } + }); + + expect(agendaFork.forkHelper?.path).to.be.eq('./test/helpers/forkHelper.ts'); + + const job = agendaFork.create('some job', { failIt: 'die' }); + job.forkMode(true); + job.schedule('now'); + await job.save(); + + const jobData = await agenda.db.getJobById(job.attrs._id as any); + + if (!jobData) { + throw new Error('job not found'); + } + + expect(jobData.fork).to.be.eq(true); + + // initialize job definition (keep in a seperate file to have a easier fork mode implementation) + someJobDefinition(agendaFork); + + await agendaFork.start(); + + do { + // console.log('.'); + await delay(50); + } while (await job.isRunning()); + + const jobDataFinished = await agenda.db.getJobById(job.attrs._id as any); + expect(jobDataFinished?.lastFinishedAt).to.not.be.eq(undefined); + expect(jobDataFinished?.failReason).to.not.be.eq(null); + expect(jobDataFinished?.failCount).to.be.eq(1); + }); + }); +}); diff --git a/test/jobprocessor.test.ts b/test/jobprocessor.test.ts new file mode 100644 index 0000000..9e92e43 --- /dev/null +++ b/test/jobprocessor.test.ts @@ -0,0 +1,280 @@ +/* eslint-disable no-console */ +import { fail } from 'assert'; +import { expect } from 'chai'; + +import { Db } from 'mongodb'; +import { Agenda } from '../src'; +import { mockMongo } from './helpers/mock-mongodb'; + +// Create agenda instances +let agenda: Agenda; +// mongo db connection db instance +let mongoDb: Db; + +const clearJobs = async () => { + if (mongoDb) { + await mongoDb.collection('agendaJobs').deleteMany({}); + } +}; + +describe('JobProcessor', () => { + // this.timeout(1000000); + + beforeEach(async () => { + if (!mongoDb) { + const mockedMongo = await mockMongo(); + // mongoCfg = mockedMongo.uri; + mongoDb = mockedMongo.mongo.db(); + } + + return new Promise(resolve => { + agenda = new Agenda( + { + mongo: mongoDb, + maxConcurrency: 4, + defaultConcurrency: 1, + lockLimit: 15, + defaultLockLimit: 6, + processEvery: '1 second', + name: 'agendaTest' + }, + async () => { + await clearJobs(); + return resolve(); + } + ); + }); + }); + + afterEach(async () => { + await agenda.stop(); + await clearJobs(); + }); + + describe('getRunningStats', () => { + it('throws an error when agenda is not running', async () => { + try { + await agenda.getRunningStats(); + fail(); + } catch (err: any) { + expect(err.message).to.be.equal('agenda not running!'); + } + }); + + it('contains the agendaVersion', async () => { + await agenda.start(); + + const status = await agenda.getRunningStats(); + expect(status).to.have.property('version'); + expect(status.version).to.match(/\d+.\d+.\d+/); + }); + + it('shows the correct job status', async () => { + agenda.define('test', async () => { + await new Promise(resolve => { + setTimeout(resolve, 30000); + }); + }); + + agenda.now('test'); + await agenda.start(); + + await new Promise(resolve => { + agenda.on('start:test', resolve); + }); + + const status = await agenda.getRunningStats(); + expect(status).to.have.property('jobStatus'); + if (status.jobStatus) { + expect(status.jobStatus).to.have.property('test'); + expect(status.jobStatus.test.locked).to.be.equal(1); + expect(status.jobStatus.test.running).to.be.equal(1); + expect(status.jobStatus.test.config.fn).to.be.a('function'); + expect(status.jobStatus.test.config.concurrency).to.be.equal(1); + expect(status.jobStatus.test.config.lockLifetime).to.be.equal(600000); + expect(status.jobStatus.test.config.priority).to.be.equal(0); + expect(status.jobStatus.test.config.lockLimit).to.be.equal(6); + } + }); + + it('shows isLockingOnTheFly', async () => { + await agenda.start(); + + const status = await agenda.getRunningStats(); + expect(status).to.have.property('isLockingOnTheFly'); + expect(status.isLockingOnTheFly).to.be.a('boolean'); + expect(status.isLockingOnTheFly).to.be.equal(false); + }); + + it('shows queueName', async () => { + await agenda.start(); + + const status = await agenda.getRunningStats(); + expect(status).to.have.property('queueName'); + expect(status.queueName).to.be.a('string'); + expect(status.queueName).to.be.equal('agendaTest'); + }); + + it('shows totalQueueSizeDB', async () => { + await agenda.start(); + + const status = await agenda.getRunningStats(); + expect(status).to.have.property('totalQueueSizeDB'); + expect(status.totalQueueSizeDB).to.be.a('number'); + expect(status.totalQueueSizeDB).to.be.equal(0); + }); + }); + + it('ensure new jobs are always filling up running queue', async () => { + let shortOneFinished = false; + + agenda.define('test long', async () => { + await new Promise(resolve => { + setTimeout(resolve, 1000); + }); + }); + agenda.define('test short', async () => { + shortOneFinished = true; + await new Promise(resolve => { + setTimeout(resolve, 5); + }); + }); + + await agenda.start(); + + // queue up long ones + for (let i = 0; i < 100; i += 1) { + agenda.now('test long'); + } + + await new Promise(resolve => { + setTimeout(resolve, 1000); + }); + + // queue more short ones (they should complete first!) + for (let j = 0; j < 100; j += 1) { + agenda.now('test short'); + } + + await new Promise(resolve => { + setTimeout(resolve, 1000); + }); + + expect(shortOneFinished).to.be.equal(true); + }); + + it('ensure slow jobs time out', async () => { + let jobStarted = false; + agenda.define( + 'test long', + async () => { + jobStarted = true; + await new Promise(resolve => { + setTimeout(resolve, 2500); + }); + }, + { lockLifetime: 500 } + ); + + // queue up long ones + agenda.now('test long'); + + await agenda.start(); + + const promiseResult = await new Promise(resolve => { + agenda.on('error', err => { + resolve(err); + }); + + agenda.on('success', () => { + resolve(); + }); + }); + + expect(jobStarted).to.be.equal(true); + expect(promiseResult).to.be.an('error'); + }); + + it('ensure slow jobs do not time out when calling touch', async () => { + agenda.define( + 'test long', + async job => { + for (let i = 0; i < 10; i += 1) { + await new Promise(resolve => { + setTimeout(resolve, 100); + }); + await job.touch(); + } + }, + { lockLifetime: 500 } + ); + + await agenda.start(); + + // queue up long ones + agenda.now('test long'); + + const promiseResult = await new Promise(resolve => { + agenda.on('error', err => { + resolve(err); + }); + + agenda.on('success', () => { + resolve(); + }); + }); + + expect(promiseResult).to.not.be.an('error'); + }); + + it('ensure concurrency is filled up', async () => { + agenda.maxConcurrency(300); + agenda.lockLimit(150); + agenda.defaultLockLimit(20); + agenda.defaultConcurrency(10); + + for (let jobI = 0; jobI < 10; jobI += 1) { + agenda.define( + `test job ${jobI}`, + async () => { + await new Promise(resolve => { + setTimeout(resolve, 5000); + }); + }, + { lockLifetime: 10000 } + ); + } + + // queue up jobs + for (let jobI = 0; jobI < 10; jobI += 1) { + for (let jobJ = 0; jobJ < 25; jobJ += 1) { + agenda.now(`test job ${jobI}`); + } + } + + await agenda.start(); + + let runningJobs = 0; + const allJobsStarted = new Promise(async resolve => { + do { + runningJobs = (await agenda.getRunningStats()).runningJobs as number; + await new Promise(wait => { + setTimeout(wait, 50); + }); + } while (runningJobs < 90); // @todo Why not 100? + resolve('all started'); + }); + + expect( + await Promise.race([ + allJobsStarted, + new Promise(resolve => { + setTimeout( + () => resolve(`not all jobs started, currently running: ${runningJobs}`), + 1500 + ); + }) + ]) + ).to.equal('all started'); + }); +}); diff --git a/test/retry.test.ts b/test/retry.test.ts new file mode 100644 index 0000000..85dcdaf --- /dev/null +++ b/test/retry.test.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ +import { Db } from 'mongodb'; +import delay from 'delay'; +import { mockMongo } from './helpers/mock-mongodb'; + +import { Agenda } from '../src'; + +// agenda instances +let agenda: Agenda; +// mongo db connection db instance +let mongoDb: Db; + +const clearJobs = async (): Promise => { + if (mongoDb) { + await mongoDb.collection('agendaJobs').deleteMany({}); + } +}; + +const jobType = 'do work'; +const jobProcessor = () => {}; + +describe('Retry', () => { + beforeEach(async () => { + if (!mongoDb) { + const mockedMongo = await mockMongo(); + mongoDb = mockedMongo.mongo.db(); + } + + return new Promise(resolve => { + agenda = new Agenda( + { + mongo: mongoDb + }, + async () => { + await delay(50); + await clearJobs(); + agenda.define('someJob', jobProcessor); + agenda.define('send email', jobProcessor); + agenda.define('some job', jobProcessor); + agenda.define(jobType, jobProcessor); + return resolve(); + } + ); + }); + }); + + afterEach(async () => { + await delay(50); + await agenda.stop(); + await clearJobs(); + // await mongoClient.disconnect(); + // await jobs._db.close(); + }); + + it('should retry a job', async () => { + let shouldFail = true; + + agenda.processEvery(100); // Shave 5s off test runtime :grin: + agenda.define('a job', (_job, done) => { + if (shouldFail) { + shouldFail = false; + return done(new Error('test failure')); + } + + done(); + return undefined; + }); + + agenda.on('fail:a job', (err, job) => { + if (err) { + // Do nothing as this is expected to fail. + } + + job.schedule('now').save(); + }); + + const successPromise = new Promise(resolve => { + agenda.on('success:a job', resolve); + }); + + await agenda.now('a job'); + + await agenda.start(); + await successPromise; + }).timeout(100000); +}); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..e97f472 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "../", + "noEmit": true + }, + "exclude": [], + "include": ["../src", "./"] +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..a5accd3 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.js", + "src/**/*.ts", + "test/**/*.js", + "test/**/*.ts", + "*.js" + ], + "compilerOptions": { + "allowJs": true, + "checkJs": true + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a3b8782 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + + // target settings for node 10 + "module": "commonjs", + "target": "es2019", + "lib": ["es2019"], + // "lib": ["esnext", "esnext.asynciterable", "dom"], + + // other best practice configs + "moduleResolution": "node", + "strict": true, + "noImplicitAny": false, // <-- get rid of this! + "removeComments": false, // <-- do not remove comments (needed for @deprecated notices etc) + "emitDecoratorMetadata": true, + "composite": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "resolveJsonModule": true, + "sourceMap": true, + "isolatedModules": false, + "declaration": true, + }, + "exclude": ["node_modules", "**/__tests__"], + "include": ["./src"] +}