From 97a65e109d473cb037e2fb4690c18ac897d5316a Mon Sep 17 00:00:00 2001 From: Pierre Cavin Date: Tue, 26 Sep 2023 20:14:27 +0200 Subject: [PATCH] refactor!: migrate to TypeScript (#694) BREAKING CHANGE: removed `cron.job()` method in favor of `new CronJob(...args)` / `CronJob.from(argsObject)` BREAKING CHANGE: removed `cron.time()` method in favor of `new CronTime()` BREAKING CHANGE: `CronJob`: constructor no longer accepts an object as its first and only params. Use `CronJob.from(argsObject)` instead. BREAKING CHANGE: `CronJob`: callbacks are now called in the order they were registered --- .eslintrc | 71 +- .github/PULL_REQUEST_TEMPLATE.md | 8 + .github/workflows/release.yml | 4 +- .github/workflows/test.yml | 7 +- .gitignore | 133 +- .prettierignore | 3 + .vscode/launch.json | 17 + README.md | 118 +- bower.json | 45 +- examples/at_10_minutes.js | 4 +- examples/at_midnight.js | 4 +- examples/basic.js | 4 +- examples/complex_expr.js | 4 +- examples/every_10_minutes.js | 4 +- examples/every_30_minutes_between_9_and_5.js | 4 +- examples/get_next_runs.js | 6 +- examples/in_the_past.js | 4 +- examples/is_crontime_valid.js | 6 +- examples/is_job_running.js | 4 +- examples/long_running_on_tick.js | 6 +- examples/mon_to_fri_at_11_30.js | 4 +- examples/multiple_jobs.js | 6 +- examples/object_param.js | 8 +- examples/run_at_specific_date.js | 8 +- examples/time_dom_syntax_with_tz.js | 4 +- examples/utc_offset_syntax.js | 6 +- jest.config.json | 13 + lib/cron.js | 55 - lib/job.js | 215 -- lib/time.js | 843 ----- package-lock.json | 2939 ++++-------------- package.json | 41 +- src/constants.ts | 81 + src/index.ts | 11 + src/job.ts | 215 ++ src/time.ts | 825 +++++ src/types/cron.types.ts | 72 + src/types/utils.ts | 14 + src/utils.ts | 7 + tests/{cron.test.js => cron.test.ts} | 182 +- tests/{crontime.test.js => crontime.test.ts} | 477 +-- tsconfig.build.json | 4 + tsconfig.json | 32 + types/index.d.ts | 181 -- types/index.test-d.ts | 85 - 45 files changed, 2587 insertions(+), 4197 deletions(-) create mode 100644 .prettierignore create mode 100644 .vscode/launch.json create mode 100644 jest.config.json delete mode 100644 lib/cron.js delete mode 100644 lib/job.js delete mode 100644 lib/time.js create mode 100644 src/constants.ts create mode 100644 src/index.ts create mode 100644 src/job.ts create mode 100644 src/time.ts create mode 100644 src/types/cron.types.ts create mode 100644 src/types/utils.ts create mode 100644 src/utils.ts rename tests/{cron.test.js => cron.test.ts} (85%) rename tests/{crontime.test.js => crontime.test.ts} (51%) create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json delete mode 100644 types/index.d.ts delete mode 100644 types/index.test-d.ts diff --git a/.eslintrc b/.eslintrc index b8100747..0e3ca183 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,25 +1,60 @@ { - "extends": [ - "standard", - "plugin:jest/recommended", - "plugin:import/recommended", - "plugin:n/recommended", - "plugin:prettier/recommended", - "plugin:promise/recommended" - ], + "parser": "@typescript-eslint/parser", "parserOptions": { - "ecmaVersion": 2018 - }, - "globals": { - "define": "readonly" + "project": "tsconfig.json", + "sourceType": "module" }, + "plugins": ["@typescript-eslint/eslint-plugin"], + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:@typescript-eslint/strict", + "plugin:prettier/recommended" + ], + "root": true, "env": { - "browser": true, - "es6": true, - "node": true, - "jest/globals": true + "node": true }, "rules": { - "jest/no-done-callback": "off" - } + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_" + } + ], + "@typescript-eslint/naming-convention": [ + "warn", + { + "selector": ["typeLike"], + "format": ["PascalCase"] + }, + { + "selector": ["variableLike", "function"], + "format": ["camelCase"], + "leadingUnderscore": "allow" + }, + { + "selector": ["variable"], + "format": ["camelCase", "UPPER_CASE"] + }, + { + "selector": "variable", + "types": ["boolean"], + "format": ["PascalCase"], + "prefix": ["is", "should", "has", "can", "did", "was", "will"] + } + ] + }, + "overrides": [ + { + "files": ["tests/**/*.ts"], + "plugins": ["jest"], + "extends": ["plugin:jest/recommended", "plugin:jest/style"], + "rules": { + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/unbound-method": "off", + "jest/no-done-callback": "off" + } + } + ] } diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6dde6531..423eb8b9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,18 +3,22 @@ ## Description + ## Related Issue + ## Motivation and Context + ## How Has This Been Tested? + @@ -22,14 +26,18 @@ ## Screenshots (if appropriate): ## Types of changes + + - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist: + + - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f757a84..dac7663c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest env: - node-version: 18.x + node-version: 20.x steps: - name: Checkout project @@ -30,6 +30,8 @@ jobs: node-version: ${{ env.node-version }} - name: Install packages run: npm ci + - name: Build project + run: npm run build - name: Run Semantic Release run: npm run release env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 42fd506e..3d1693d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node: [14.x, 16.x, 18.x] + node: [16.x, 18.x, 20.x] os: [ubuntu-latest, windows-latest, macos-latest] steps: @@ -23,11 +23,12 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} + cache: 'npm' - name: Install packages run: npm ci + - name: Build project + run: npm run build - name: Check codestyle compliance run: npm run lint - - name: Check TS types - run: npm run test:types - name: Run tests run: npm run test diff --git a/.gitignore b/.gitignore index 5c53a45b..c6bba591 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,130 @@ -*.sw[a-z] -node_modules -coverage \ No newline at end of file +# Logs +logs +*.log +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 + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..b50aeaad --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +CHANGELOG.md +dist/ +coverage/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..953f3bbc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "1.0.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Jest Debug", + "env": { "NODE_ENV": "test" }, + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": [], + "console": "integratedTerminal", + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + } + } + ] +} diff --git a/README.md b/README.md index c6171dc0..110444e6 100644 --- a/README.md +++ b/README.md @@ -12,59 +12,70 @@ [![Known Vulnerabilities](https://snyk.io/test/github/kelektiv/node-cron/badge.svg)](https://snyk.io/test/github/kelektiv/node-cron) [![Minified size](https://badgen.net/bundlephobia/min/cron)](https://badgen.net/bundlephobia/min/cron) [![Minzipped size](https://badgen.net/bundlephobia/minzip/cron)](https://badgen.net/bundlephobia/minzip/cron) -[![monthly downloads](https://badgen.net/npm/dm/cron?icon=npm)](https://badgen.net/npm/dm/cron) +[![monthly downloads](https://badgen.net/npm/dm/cron?icon=npm)](https://badgen.net/npm/dm/cron) Cron is a tool that allows you to execute _something_ on a schedule. This is typically done using the cron syntax. We allow you to: - execute a function whenever your scheduled job triggers -- execute a job external to the javascript process using `child_process` -- use a Date object instead of cron syntax as the trigger for your callback +- execute a job external to the javascript process (like a system command) using `child_process` +- use a Date or Luxon DateTime object instead of cron syntax as the trigger for your callback - use an additional slot for seconds (leaving it off will default to 0 and match the Unix behavior) -## Installation +## Installation ``` -npm install cron +npm install cron ``` -## Migrating from v2 to v3 +## Migrating from v2 to v3 -In version 3 of this library, we aligned our format for the cron patterns with the UNIX format. See below for the changes you need to make when upgrading: +In version 3 of this library, we migrated to TypeScript, aligned our cron patterns format with the UNIX standard, and released some other breaking changes. See below for the changes you need to make when upgrading:
Migrating from v2 to v3 - ### Month & day-of-week indexing changes - - **Month indexing went from `0-11` to `1-12`. So you need to increment all numeric months by 1.** +### Month & day-of-week indexing changes - For day-of-week indexing, we only added support for `7` as Sunday, so you don't need to change anything ! +**Month indexing went from `0-11` to `1-12`. So you need to increment all numeric months by 1.** + +For day-of-week indexing, we only added support for `7` as Sunday, so you don't need to change anything ! + +### CronJob changes + +- **constructor no longer accepts an object as its first and only params. Use `CronJob.from(argsObject)` instead.** +- **callbacks are now called in the order they were registered.** + +### Removed methods + +- **removed `job()` method in favor of `new CronJob(...args)` / `CronJob.from(argsObject)`** + +- **removed `time()` method in favor of `new CronTime()`**
## Versions and Backwards compatibility breaks -As goes with semver, breaking backwards compatibility should be explicit in the versioning of your library. As such, we'll upgrade the version of this module in accordance with breaking changes (We're not always great about doing it this way so if you notice that there are breaking changes that haven't been bumped appropriately please let us know). +As goes with semver, breaking backwards compatibility should be explicit in the versioning of your library. As such, we'll upgrade the version of this module in accordance with breaking changes (We're not always great about doing it this way so if you notice that there are breaking changes that haven't been bumped appropriately please let us know). ## Usage (basic cron usage) ```javascript var CronJob = require('cron').CronJob; var job = new CronJob( - '* * * * * *', - function () { - console.log('You will see this message every second'); - }, - null, - true, - 'America/Los_Angeles' + '* * * * * *', + function () { + console.log('You will see this message every second'); + }, + null, + true, + 'America/Los_Angeles' ); // job.start() - See note below when to use this -``` +``` Note - In the example above, the 4th parameter of `CronJob()` automatically starts the job on initialization. If this parameter is falsy or not provided, the job needs to be explicitly started using `job.start()`. -There are more examples available in this repository at: [/examples](https://github.com/kelektiv/node-cron/tree/main/examples) +There are more examples available in this repository at: [/examples](https://github.com/kelektiv/node-cron/tree/main/examples) ## Available Cron patterns @@ -74,11 +85,11 @@ Ranges e.g. 1-3,5 Steps e.g. */2 ``` -[Read up on cron patterns here](http://crontab.org). Note the examples in the link have five fields, and 1 minute as the finest granularity, but this library has six fields, with 1 second as the finest granularity. +[Read up on cron patterns here](http://crontab.org). Note the examples in the link have five fields, and 1 minute as the finest granularity, but this library has six fields, with 1 second as the finest granularity. -There are tools that help when constructing your cronjobs. You might find something like https://crontab.guru/ or https://cronjob.xyz/ helpful. But, note that these don't necessarily accept the exact same syntax as this library, for instance, it doesn't accept the `seconds` field, so keep that in mind. +There are tools that help when constructing your cronjobs. You might find something like https://crontab.guru/ or https://cronjob.xyz/ helpful. But, note that these don't necessarily accept the exact same syntax as this library, for instance, it doesn't accept the `seconds` field, so keep that in mind. -### Cron Ranges +### Cron Ranges This library follows the [UNIX Cron format](https://man7.org/linux/man-pages/man5/crontab.5.html), with an added field at the beginning for second granularity. @@ -96,42 +107,43 @@ day of week 0-7 (0 or 7 is Sunday, or use names) > Names can also be used for the 'month' and 'day of week' fields. Use the first three letters of the particular day or month (case does not matter). Ranges and lists of names are allowed. > Examples: "mon,wed,fri", "jan-mar". -## Gotchas +## Gotchas - Months are indexed as 0-11 instead of 1-12. This is different from Unix `cron` and is planned to updated to match Unix `cron` in v3.0.0 of `node-cron`. - Millisecond level granularity in JS `Date` or Luxon `DateTime` objects: Because computers take time to do things, there may be some delay in execution. This should be on the order of milliseconds. This module doesn't allow MS level granularity for the regular cron syntax, but _does_ allow you to specify a real date of execution in either a javascript `Date` object or a Luxon `DateTime` object. When this happens you may find that you aren't able to execute a job that _should_ run in the future like with `new Date().setMilliseconds(new Date().getMilliseconds() + 1)`. This is due to those cycles of execution above. This wont be the same for everyone because of compute speed. When we tried it locally we saw that somewhere around the 4-5 ms mark was where we got consistent ticks using real dates, but anything less than that would result in an exception. This could be really confusing. We could restrict the granularity for all dates to seconds, but felt that it wasn't a huge problem so long as you were made aware. If this becomes more of an issue, We can revisit it. -- Arrow Functions for `onTick`: Arrow functions get their `this` context from their parent scope. Thus, if you use them, you will not get the `this` context of the cronjob. You can read a little more in issue [GH-47](https://github.com/kelektiv/node-cron/issues/47#issuecomment-459762775) +- Arrow Functions for `onTick`: Arrow functions get their `this` context from their parent scope. Thus, if you use them, you will not get the `this` context of the cronjob. You can read a little more in issue [GH-47](https://github.com/kelektiv/node-cron/issues/47#issuecomment-459762775) -## API +## API -Parameter Based +Parameter Based -- `job` - shortcut to `new cron.CronJob()`. -- `time` - shortcut to `new cron.CronTime()`. - `sendAt` - tells you when a `CronTime` will be run. - `timeout` - tells you when the next timeout is. - `CronJob` - - `constructor(cronTime, onTick, onComplete, start, timeZone, context, runOnInit, utcOffset, unrefTimeout)` - Of note, the first parameter here can be a JSON object that has the below names and associated types (see examples above). - - `cronTime` - [REQUIRED] - The time to fire off your job. This can be in the form of cron syntax or a JS [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date) object. - - `onTick` - [REQUIRED] - The function to fire at the specified time. If an `onComplete` callback was provided, `onTick` will receive it as an argument. `onTick` may call `onComplete` when it has finished its work. - - `onComplete` - [OPTIONAL] - A function that will fire when the job is stopped with `job.stop()`, and may also be called by `onTick` at the end of each run. - - `start` - [OPTIONAL] - Specifies whether to start the job just before exiting the constructor. By default this is set to false. If left at default you will need to call `job.start()` in order to start the job (assuming `job` is the variable you set the cronjob to). This does not immediately fire your `onTick` function, it just gives you more control over the behavior of your jobs. - - `timeZone` - [OPTIONAL] - Specify the time zone for the execution. This will modify the actual time relative to your time zone. If the time zone is invalid, an error is thrown. By default (if this is omitted) the local time zone will be used. You can check the various time zones format accepted in the [Luxon documentation](https://github.com/moment/luxon/blob/master/docs/zones.md#specifying-a-zone). Note: This parameter supports minutes offsets, e.g. `UTC+5:30`. **Warning**: Probably don't use both `timeZone` and `utcOffset` together or weird things may happen. - - `context` - [OPTIONAL] - The context within which to execute the onTick method. This defaults to the cronjob itself allowing you to call `this.stop()`. However, if you change this you'll have access to the functions and values within your context object. - - `runOnInit` - [OPTIONAL] - This will immediately fire your `onTick` function as soon as the requisite initialization has happened. This option is set to `false` by default for backwards compatibility. - - `utcOffset` - [OPTIONAL] - This allows you to specify the offset of your time zone rather than using the `timeZone` param. This should be an integer representing the number of minutes offset (like `120` for +2 hours or `-90` for -1.5 hours). **Warning**: Minutes offsets < 60 and >-60 will be treated as an offset in hours. This means a minute offset of `30` means an offset of +30 hours. Use the `timeZone` param in this case. This behavior [is planned to be removed in V3](https://github.com/kelektiv/node-cron/pull/685#issuecomment-1676417917). **Warning**: Probably don't use both `timeZone` and `utcOffset` together or weird things may happen. - - `unrefTimeout` - [OPTIONAL] - If you have code that keeps the event loop running and want to stop the node process when that finishes regardless of the state of your cronjob, you can do so making use of this parameter. This is off by default and cron will run as if it needs to control the event loop. For more information take a look at [timers#timers_timeout_unref](https://nodejs.org/api/timers.html#timers_timeout_unref) from the NodeJS docs. - - `start` - Runs your job. - - `stop` - Stops your job. - - `setTime` - Stops and changes the time for the `CronJob`. Param must be a `CronTime`. - - `lastDate` - Tells you the last execution date. - - `nextDate` - Provides the next date that will trigger an `onTick`. - - `nextDates` - Provides an array of the next set of dates that will trigger an `onTick`. - - `fireOnTick` - Allows you to override the `onTick` calling behavior. This matters so only do this if you have a really good reason to do so. - - `addCallback` - Allows you to add `onTick` callbacks. + - `constructor(cronTime, onTick, onComplete, start, timeZone, context, runOnInit, utcOffset, unrefTimeout)` + - `cronTime` - [REQUIRED] - The time to fire off your job. This can be in the form of cron syntax or a JS [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date) object. + - `onTick` - [REQUIRED] - The function to fire at the specified time. If an `onComplete` callback was provided, `onTick` will receive it as an argument. `onTick` may call `onComplete` when it has finished its work. + - `onComplete` - [OPTIONAL] - A function that will fire when the job is stopped with `job.stop()`, and may also be called by `onTick` at the end of each run. + - `start` - [OPTIONAL] - Specifies whether to start the job just before exiting the constructor. By default this is set to false. If left at default you will need to call `job.start()` in order to start the job (assuming `job` is the variable you set the cronjob to). This does not immediately fire your `onTick` function, it just gives you more control over the behavior of your jobs. + - `timeZone` - [OPTIONAL] - Specify the time zone for the execution. This will modify the actual time relative to your time zone. If the time zone is invalid, an error is thrown. By default (if this is omitted) the local time zone will be used. You can check the various time zones format accepted in the [Luxon documentation](https://github.com/moment/luxon/blob/master/docs/zones.md#specifying-a-zone). Note: This parameter supports minutes offsets, e.g. `UTC+5:30`. **Warning**: Probably don't use both `timeZone` and `utcOffset` together or weird things may happen. + - `context` - [OPTIONAL] - The context within which to execute the onTick method. This defaults to the cronjob itself allowing you to call `this.stop()`. However, if you change this you'll have access to the functions and values within your context object. + - `runOnInit` - [OPTIONAL] - This will immediately fire your `onTick` function as soon as the requisite initialization has happened. This option is set to `false` by default for backwards compatibility. + - `utcOffset` - [OPTIONAL] - This allows you to specify the offset of your time zone rather than using the `timeZone` param. This should be an integer representing the number of minutes offset (like `120` for +2 hours or `-90` for -1.5 hours). **Warning**: Minutes offsets < 60 and >-60 will be treated as an offset in hours. This means a minute offset of `30` means an offset of +30 hours. Use the `timeZone` param in this case. This behavior [is planned to be removed in V3](https://github.com/kelektiv/node-cron/pull/685#issuecomment-1676417917). **Warning**: Probably don't use both `timeZone` and `utcOffset` together or weird things may happen. + - `unrefTimeout` - [OPTIONAL] - If you have code that keeps the event loop running and want to stop the node process when that finishes regardless of the state of your cronjob, you can do so making use of this parameter. This is off by default and cron will run as if it needs to control the event loop. For more information take a look at [timers#timers_timeout_unref](https://nodejs.org/api/timers.html#timers_timeout_unref) from the NodeJS docs. + - `from` (static) - Create a new CronJob object providing arguments as an object. See argument names and descriptions above. + - `start` - Runs your job. + - `stop` - Stops your job. + - `setTime` - Stops and changes the time for the `CronJob`. Param must be a `CronTime`. + - `lastDate` - Tells you the last execution date. + - `nextDate` - Provides the next date that will trigger an `onTick`. + - `nextDates` - Provides an array of the next set of dates that will trigger an `onTick`. + - `fireOnTick` - Allows you to override the `onTick` calling behavior. This matters so only do this if you have a really good reason to do so. + - `addCallback` - Allows you to add `onTick` callbacks. Callbacks are run in the order they are registered. - `CronTime` - - `constructor(time)` - - `time` - [REQUIRED] - The time to fire off your job. This can be in the form of cron syntax or a JS [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date) object. + - `constructor(time, zone, utcOffset)` + - `time` - [REQUIRED] - The time to fire off your job. This can be in the form of cron syntax or a JS [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date) object. + - `zone` - [OPTIONAL] - Same as `timeZone` from `CronJob` parameters. + - `utcOffset` - [OPTIONAL] - Same as `utcOffset` from `CronJob` parameters. ## Community @@ -151,8 +163,10 @@ Because we can't magically know what you are doing to expose an issue, it is bes ### Acknowledgements -This is a community effort project. In the truest sense, this project started as an open source project from [cron.js](http://github.com/padolsey/cron.js) and grew into something else. Other people have contributed code, time, and oversight to the project. At this point there are too many to name here so We'll just say thanks. +This is a community effort project. In the truest sense, this project started as an open source project from [cron.js](http://github.com/padolsey/cron.js) and grew into something else. Other people have contributed code, time, and oversight to the project. At this point there are too many to name here so we'll just say thanks. + +Special thanks to [Hiroki Horiuchi](https://github.com/horiuchi), [Lundarl Gholoi](https://github.com/winup) and [koooge](https://github.com/koooge) for their work on the [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) typings before they were imported in v2.4.0. -## License +## License MIT diff --git a/bower.json b/bower.json index dffa4c46..2360cc3f 100644 --- a/bower.json +++ b/bower.json @@ -1,29 +1,20 @@ { - "name": "cronjs", - "homepage": "https://github.com/ncb000gt/node-cron", - "authors": [ - "Romain Beauxis", - "James Padoulsey<@padolsey>", - "Craig Condon", - "Finn Herpich", - "cliftonc", - "neyric", - "humanchimp", - "danhbear", - "Jordan Abderrachid" - ], - "description": "js cron handler", - "main": ["lib/cron.js"], - "keywords": [ - "cron", - "js" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] + "name": "cronjs", + "homepage": "https://github.com/ncb000gt/node-cron", + "authors": [ + "Romain Beauxis", + "James Padoulsey<@padolsey>", + "Craig Condon", + "Finn Herpich", + "cliftonc", + "neyric", + "humanchimp", + "danhbear", + "Jordan Abderrachid" + ], + "description": "js cron handler", + "main": ["dist/index.js"], + "keywords": ["cron", "js"], + "license": "MIT", + "ignore": ["**/.*", "node_modules", "bower_components", "test", "tests"] } diff --git a/examples/at_10_minutes.js b/examples/at_10_minutes.js index 7568255a..dfcf376c 100644 --- a/examples/at_10_minutes.js +++ b/examples/at_10_minutes.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('* 10 * * * *', function() { +const job = new CronJob('* 10 * * * *', function () { const d = new Date(); console.log('At Ten Minutes:', d); }); diff --git a/examples/at_midnight.js b/examples/at_midnight.js index b276bcb3..204475fa 100644 --- a/examples/at_midnight.js +++ b/examples/at_midnight.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('00 00 00 * * *', function() { +const job = new CronJob('00 00 00 * * *', function () { const d = new Date(); console.log('Midnight:', d); }); diff --git a/examples/basic.js b/examples/basic.js index 9cab584d..94b5229d 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('* * * * * *', function() { +const job = new CronJob('* * * * * *', function () { const d = new Date(); console.log('Every second:', d); }); diff --git a/examples/complex_expr.js b/examples/complex_expr.js index c02a31c5..9188ca0a 100644 --- a/examples/complex_expr.js +++ b/examples/complex_expr.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('* 4-22 * * 1-5', function() { +const job = new CronJob('* 4-22 * * 1-5', function () { const d = new Date(); console.log('Every Minute Between hours 4-22, Monday through Friday:', d); }); diff --git a/examples/every_10_minutes.js b/examples/every_10_minutes.js index 0d71e428..511bce2a 100644 --- a/examples/every_10_minutes.js +++ b/examples/every_10_minutes.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('0 */10 * * * *', function() { +const job = new CronJob('0 */10 * * * *', function () { const d = new Date(); console.log('Every Tenth Minute:', d); }); diff --git a/examples/every_30_minutes_between_9_and_5.js b/examples/every_30_minutes_between_9_and_5.js index 09791d50..73a269dd 100644 --- a/examples/every_30_minutes_between_9_and_5.js +++ b/examples/every_30_minutes_between_9_and_5.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('0 */30 9-17 * * *', function() { +const job = new CronJob('0 */30 9-17 * * *', function () { const d = new Date(); console.log('Every 30 minutes between 9-17:', d); }); diff --git a/examples/get_next_runs.js b/examples/get_next_runs.js index f77483f4..eab53870 100644 --- a/examples/get_next_runs.js +++ b/examples/get_next_runs.js @@ -1,8 +1,8 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; const job = new CronJob( '0 * * * * *', - function() { + function () { console.log('Date: ', new Date()); }, null, @@ -13,7 +13,7 @@ console.log('System TZ next 5: ', job.nextDates(5)); const jobUTC = new CronJob( '0 * * * * *', - function() { + function () { console.log('Date: ', new Date()); }, null, diff --git a/examples/in_the_past.js b/examples/in_the_past.js index cdaf1e23..1685ec74 100644 --- a/examples/in_the_past.js +++ b/examples/in_the_past.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; // XXX: SEE README GOTCHAS ABOUT WHY THIS COULD BE IN THE PAST! -let d = new Date(); +const d = new Date(); d.setMilliseconds(d.getMilliseconds() + 1); console.log('Before job instantiation'); diff --git a/examples/is_crontime_valid.js b/examples/is_crontime_valid.js index 11c22a52..915f9b1d 100644 --- a/examples/is_crontime_valid.js +++ b/examples/is_crontime_valid.js @@ -1,9 +1,9 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; try { new CronJob('NOT VALID', () => { - console.log('shouldn\'t get printed'); + console.log("shouldn't get printed"); }); -} catch(e) { +} catch (e) { console.log('omg err', e); } diff --git a/examples/is_job_running.js b/examples/is_job_running.js index 51df46fe..453a980e 100644 --- a/examples/is_job_running.js +++ b/examples/is_job_running.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('* * * * * *', function() { +const job = new CronJob('* * * * * *', function () { const d = new Date(); console.log('Every second:', d); }); diff --git a/examples/long_running_on_tick.js b/examples/long_running_on_tick.js index 371e9420..bba12727 100644 --- a/examples/long_running_on_tick.js +++ b/examples/long_running_on_tick.js @@ -1,15 +1,15 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; let isRunning = false; console.log('Before job instantiation'); -const job = new CronJob('* * * * * *', function() { +const job = new CronJob('* * * * * *', function () { const d = new Date(); console.log('Check every second:', d, ', isRunning: ', isRunning); if (!isRunning) { isRunning = true; - setTimeout(function() { + setTimeout(function () { console.log('Long running onTick complete:', new Date()); isRunning = false; }, 3000); diff --git a/examples/mon_to_fri_at_11_30.js b/examples/mon_to_fri_at_11_30.js index 845d6296..296e697d 100644 --- a/examples/mon_to_fri_at_11_30.js +++ b/examples/mon_to_fri_at_11_30.js @@ -1,7 +1,7 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('00 30 11 * * 1-5', function() { +const job = new CronJob('00 30 11 * * 1-5', function () { const d = new Date(); console.log('onTick:', d); }); diff --git a/examples/multiple_jobs.js b/examples/multiple_jobs.js index 5942db6b..7fea9d14 100644 --- a/examples/multiple_jobs.js +++ b/examples/multiple_jobs.js @@ -1,12 +1,12 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -const job = new CronJob('*/5 * * * * *', function() { +const job = new CronJob('*/5 * * * * *', function () { const d = new Date(); console.log('First:', d); }); -const job2 = new CronJob('*/8 * * * * *', function() { +const job2 = new CronJob('*/8 * * * * *', function () { const d = new Date(); console.log('Second:', d); }); diff --git a/examples/object_param.js b/examples/object_param.js index 05294fc8..a62a752d 100644 --- a/examples/object_param.js +++ b/examples/object_param.js @@ -1,17 +1,17 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; let isRunning = false; console.log('Before job instantiation'); -const job = new CronJob({ +const job = CronJob.from({ cronTime: '* * * * * *', - onTick: function() { + onTick: function () { const d = new Date(); console.log('Check every second:', d, ', isRunning: ', isRunning); if (!isRunning) { isRunning = true; - setTimeout(function() { + setTimeout(function () { console.log('Long running onTick complete:', new Date()); isRunning = false; }, 3000); diff --git a/examples/run_at_specific_date.js b/examples/run_at_specific_date.js index 03acc589..2d8e21c4 100644 --- a/examples/run_at_specific_date.js +++ b/examples/run_at_specific_date.js @@ -1,9 +1,9 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('Before job instantiation'); -let date = new Date(); -date.setSeconds(date.getSeconds()+2); -const job = new CronJob(date, function() { +const date = new Date(); +date.setSeconds(date.getSeconds() + 2); +const job = new CronJob(date, function () { const d = new Date(); console.log('Specific date:', date, ', onTick at:', d); }); diff --git a/examples/time_dom_syntax_with_tz.js b/examples/time_dom_syntax_with_tz.js index 92370c77..e383981c 100644 --- a/examples/time_dom_syntax_with_tz.js +++ b/examples/time_dom_syntax_with_tz.js @@ -1,9 +1,9 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('first'); const job = new CronJob( '0 0 9 4 * *', - function() { + function () { console.log('message'); }, null, diff --git a/examples/utc_offset_syntax.js b/examples/utc_offset_syntax.js index 0408f193..e326b7a9 100644 --- a/examples/utc_offset_syntax.js +++ b/examples/utc_offset_syntax.js @@ -1,9 +1,9 @@ -const CronJob = require('../lib/cron.js').CronJob; +import { CronJob } from '../src'; console.log('first'); const job = new CronJob( '0 0 9 4 * *', - function() { + function () { console.log('message'); }, null, @@ -11,6 +11,6 @@ const job = new CronJob( null, null, null, - -360, // UTC-6, represented in minutes + -360 // UTC-6, represented in minutes ); console.log('second'); diff --git a/jest.config.json b/jest.config.json new file mode 100644 index 00000000..94db4699 --- /dev/null +++ b/jest.config.json @@ -0,0 +1,13 @@ +{ + "preset": "ts-jest", + "testEnvironment": "node", + "collectCoverage": true, + "coverageThreshold": { + "global": { + "statements": 85, + "branches": 75, + "functions": 85, + "lines": 85 + } + } +} diff --git a/lib/cron.js b/lib/cron.js deleted file mode 100644 index 5cd273ea..00000000 --- a/lib/cron.js +++ /dev/null @@ -1,55 +0,0 @@ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(['luxon'], factory); - } else if (typeof exports === 'object') { - module.exports = factory(require('luxon'), require('child_process')); - } else { - root.Cron = factory(root.luxon); - } -})(this, function (luxon, childProcess) { - const exports = {}; - const spawn = childProcess && childProcess.spawn; - const CronTime = require('./time')(luxon); - const CronJob = require('./job')(CronTime, spawn); - - /** - * Extend Luxon DateTime - */ - luxon.DateTime.prototype.getWeekDay = function () { - return this.weekday === 7 ? 0 : this.weekday; - }; - - exports.job = ( - cronTime, - onTick, - onComplete, - startNow, - timeZone, - context, - runOnInit, - utcOffset, - unrefTimeout - ) => - new CronJob( - cronTime, - onTick, - onComplete, - startNow, - timeZone, - context, - runOnInit, - utcOffset, - unrefTimeout - ); - - exports.time = (cronTime, timeZone) => new CronTime(cronTime, timeZone); - - exports.sendAt = cronTime => exports.time(cronTime).sendAt(); - - exports.timeout = cronTime => exports.time(cronTime).getTimeout(); - - exports.CronJob = CronJob; - exports.CronTime = CronTime; - - return exports; -}); diff --git a/lib/job.js b/lib/job.js deleted file mode 100644 index dbbce8d5..00000000 --- a/lib/job.js +++ /dev/null @@ -1,215 +0,0 @@ -function CronJob(CronTime, spawn) { - function fnWrap(cmd) { - let command; - let args; - - switch (typeof cmd) { - case 'string': - args = cmd.split(' '); - command = args.shift(); - - return spawn.bind(undefined, command, args); - - case 'object': - command = cmd && cmd.command; - if (command) { - args = cmd.args; - const options = cmd.options; - - return spawn.bind(undefined, command, args, options); - } - break; - } - - return cmd; - } - - function CJ( - cronTime, - onTick, - onComplete, - startNow, - timeZone, - context, - runOnInit, - utcOffset, - unrefTimeout - ) { - let _cronTime = cronTime; - let argCount = 0; - for (let i = 0; i < arguments.length; i++) { - if (arguments[i] !== undefined) { - argCount++; - } - } - - if (typeof cronTime !== 'string' && argCount === 1) { - // crontime is an object... - onTick = cronTime.onTick; - onComplete = cronTime.onComplete; - context = cronTime.context; - startNow = cronTime.start || cronTime.startNow || cronTime.startJob; - timeZone = cronTime.timeZone; - runOnInit = cronTime.runOnInit; - _cronTime = cronTime.cronTime; - utcOffset = cronTime.utcOffset; - unrefTimeout = cronTime.unrefTimeout; - } - - this.context = context || this; - this._callbacks = []; - this.onComplete = fnWrap(onComplete); - this.cronTime = new CronTime(_cronTime, timeZone, utcOffset); - this.unrefTimeout = unrefTimeout; - - addCallback.call(this, fnWrap(onTick)); - - if (runOnInit) { - this.lastExecution = new Date(); - fireOnTick.call(this); - } - - if (startNow) { - start.call(this); - } - - return this; - } - - const addCallback = function (callback) { - if (typeof callback === 'function') { - this._callbacks.push(callback); - } - }; - CJ.prototype.addCallback = addCallback; - - CJ.prototype.setTime = function (time) { - if (typeof time !== 'object') { - // crontime is an object... - throw new Error('time must be an instance of CronTime.'); - } - const wasRunning = this.running; - this.stop(); - this.cronTime = time; - if (wasRunning) this.start(); - }; - - CJ.prototype.nextDate = function () { - return this.cronTime.sendAt(); - }; - - const fireOnTick = function () { - for (let i = this._callbacks.length - 1; i >= 0; i--) { - this._callbacks[i].call(this.context, this.onComplete); - } - }; - CJ.prototype.fireOnTick = fireOnTick; - - CJ.prototype.nextDates = function (i) { - return this.cronTime.sendAt(i || 0); - }; - - const start = function () { - if (this.running) { - return; - } - - const MAXDELAY = 2147483647; // The maximum number of milliseconds setTimeout will wait. - const self = this; - let timeout = this.cronTime.getTimeout(); - let remaining = 0; - let startTime; - - if (this.cronTime.realDate) { - this.runOnce = true; - } - - function _setTimeout(timeout) { - startTime = Date.now(); - self._timeout = setTimeout(callbackWrapper, timeout); - if (self.unrefTimeout && typeof self._timeout.unref === 'function') { - self._timeout.unref(); - } - } - - // The callback wrapper checks if it needs to sleep another period or not - // and does the real callback logic when it's time. - function callbackWrapper() { - const diff = startTime + timeout - Date.now(); - - if (diff > 0) { - let newTimeout = self.cronTime.getTimeout(); - - if (newTimeout > diff) { - newTimeout = diff; - } - - remaining += newTimeout; - } - - // If there is sleep time remaining, calculate how long and go to sleep - // again. This processing might make us miss the deadline by a few ms - // times the number of sleep sessions. Given a MAXDELAY of almost a - // month, this should be no issue. - self.lastExecution = new Date(); - if (remaining) { - if (remaining > MAXDELAY) { - remaining -= MAXDELAY; - timeout = MAXDELAY; - } else { - timeout = remaining; - remaining = 0; - } - - _setTimeout(timeout); - } else { - // We have arrived at the correct point in time. - - self.running = false; - - // start before calling back so the callbacks have the ability to stop the cron job - if (!self.runOnce) { - self.start(); - } - - self.fireOnTick(); - } - } - - if (timeout >= 0) { - this.running = true; - - // Don't try to sleep more than MAXDELAY ms at a time. - - if (timeout > MAXDELAY) { - remaining = timeout - MAXDELAY; - timeout = MAXDELAY; - } - - _setTimeout(timeout); - } else { - this.stop(); - } - }; - - CJ.prototype.start = start; - - CJ.prototype.lastDate = function () { - return this.lastExecution; - }; - - /** - * Stop the cronjob. - */ - CJ.prototype.stop = function () { - if (this._timeout) clearTimeout(this._timeout); - this.running = false; - if (typeof this.onComplete === 'function') { - this.onComplete(); - } - }; - - return CJ; -} - -module.exports = CronJob; diff --git a/lib/time.js b/lib/time.js deleted file mode 100644 index 72b320d3..00000000 --- a/lib/time.js +++ /dev/null @@ -1,843 +0,0 @@ -const CONSTRAINTS = [ - [0, 59], - [0, 59], - [0, 23], - [1, 31], - [1, 12], - [0, 7] -]; -const MONTH_CONSTRAINTS = [ - 31, - 29, // support leap year...not perfect - 31, - 30, - 31, - 30, - 31, - 31, - 30, - 31, - 30, - 31 -]; -const PARSE_DEFAULTS = ['0', '*', '*', '*', '*', '*']; -const ALIASES = { - jan: 1, - feb: 2, - mar: 3, - apr: 4, - may: 5, - jun: 6, - jul: 7, - aug: 8, - sep: 9, - oct: 10, - nov: 11, - dec: 12, - sun: 0, - mon: 1, - tue: 2, - wed: 3, - thu: 4, - fri: 5, - sat: 6 -}; -const TIME_UNITS_MAP = { - SECOND: 'second', - MINUTE: 'minute', - HOUR: 'hour', - DAY_OF_MONTH: 'dayOfMonth', - MONTH: 'month', - DAY_OF_WEEK: 'dayOfWeek' -}; -const TIME_UNITS = Object.values(TIME_UNITS_MAP); -const TIME_UNITS_LEN = TIME_UNITS.length; -const PRESETS = { - '@yearly': '0 0 0 1 1 *', - '@monthly': '0 0 0 1 * *', - '@weekly': '0 0 0 * * 0', - '@daily': '0 0 0 * * *', - '@hourly': '0 0 * * * *', - '@minutely': '0 * * * * *', - '@secondly': '* * * * * *', - '@weekdays': '0 0 0 * * 1-5', - '@weekends': '0 0 0 * * 0,6' -}; -const RE_WILDCARDS = /\*/g; -const RE_RANGE = /^(\d+)(?:-(\d+))?(?:\/(\d+))?$/g; - -function CronTime(luxon) { - function CT(source, zone, utcOffset) { - this.source = source; - - if (zone) { - const dt = luxon.DateTime.fromObject({}, { zone }); - if (dt.invalid) { - throw new Error('Invalid timezone.'); - } - - this.zone = zone; - } - - if (typeof utcOffset !== 'undefined') { - this.utcOffset = utcOffset; - } - - const that = this; - TIME_UNITS.forEach(timeUnit => { - that[timeUnit] = {}; - }); - - if (this.source instanceof Date || this.source instanceof luxon.DateTime) { - if (this.source instanceof Date) { - this.source = luxon.DateTime.fromJSDate(this.source); - } - this.realDate = true; - } else { - this._parse(this.source); - this._verifyParse(); - } - } - - CT.prototype = { - /* - * Ensure that the syntax parsed correctly and correct the specified values if needed. - */ - _verifyParse: function () { - const months = Object.keys(this.month); - const dom = Object.keys(this.dayOfMonth); - let ok = false; - - /* if a dayOfMonth is not found in all months, we only need to fix the last - wrong month to prevent infinite loop */ - let lastWrongMonth = NaN; - for (let i = 0; i < months.length; i++) { - const m = months[i]; - const con = MONTH_CONSTRAINTS[parseInt(m, 10) - 1]; - - for (let j = 0; j < dom.length; j++) { - const day = dom[j]; - if (day <= con) { - ok = true; - } - } - - if (!ok) { - // save the month in order to be fixed if all months fails (infinite loop) - lastWrongMonth = m; - console.warn(`Month '${m}' is limited to '${con}' days.`); - } - } - - // infinite loop detected (dayOfMonth is not found in all months) - if (!ok) { - const notOkCon = MONTH_CONSTRAINTS[parseInt(lastWrongMonth, 10) - 1]; - for (let k = 0; k < dom.length; k++) { - const notOkDay = dom[k]; - if (notOkDay > notOkCon) { - delete this.dayOfMonth[notOkDay]; - const fixedDay = Number(notOkDay) % notOkCon; - this.dayOfMonth[fixedDay] = true; - } - } - } - }, - - /** - * Calculate the "next" scheduled time - */ - sendAt: function (i) { - let date = this.realDate ? this.source : luxon.DateTime.local(); - if (this.zone) { - date = date.setZone(this.zone); - } - - if (typeof this.utcOffset !== 'undefined') { - const offsetHours = parseInt( - this.utcOffset >= 60 || this.utcOffset <= -60 - ? this.utcOffset / 60 - : this.utcOffset - ); - - const offsetMins = - this.utcOffset >= 60 || this.utcOffset <= -60 - ? Math.abs(this.utcOffset - offsetHours * 60) - : 0; - const offsetMinsStr = offsetMins >= 10 ? offsetMins : '0' + offsetMins; - - let utcZone = 'UTC'; - - if (parseInt(this.utcOffset) < 0) { - utcZone += `${ - offsetHours === 0 ? '-0' : offsetHours - }:${offsetMinsStr}`; - } else { - utcZone += `+${offsetHours}:${offsetMinsStr}`; - } - - date = date.setZone(utcZone); - - if (date.invalid) { - throw new Error('ERROR: You specified an invalid UTC offset.'); - } - } - - if (this.realDate) { - if (luxon.DateTime.local() > date) { - throw new Error('WARNING: Date in past. Will never be fired.'); - } - - return date; - } - - if (isNaN(i) || i < 0) { - // just get the next scheduled time - return this._getNextDateFrom(date); - } else { - // return the next schedule times - const dates = []; - for (; i > 0; i--) { - date = this._getNextDateFrom(date); - dates.push(date); - } - - return dates; - } - }, - - /** - * Get the number of milliseconds in the future at which to fire our callbacks. - */ - getTimeout: function () { - return Math.max(-1, this.sendAt() - luxon.DateTime.local()); - }, - - /** - * writes out a cron string - */ - toString: function () { - return this.toJSON().join(' '); - }, - - /** - * Json representation of the parsed cron syntax. - */ - toJSON: function () { - const self = this; - return TIME_UNITS.map(function (timeName) { - return self._wcOrAll(timeName); - }); - }, - - getNextDateFrom: function (start, zone) { - return this._getNextDateFrom(start, zone); - }, - - /** - * Get next date matching the specified cron time. - * - * Algorithm: - * - Start with a start date and a parsed crontime. - * - Loop until 5 seconds have passed, or we found the next date. - * - Within the loop: - * - If it took longer than 5 seconds to select a date, throw an exception. - * - Find the next month to run at. - * - Find the next day of the month to run at. - * - Find the next day of the week to run at. - * - Find the next hour to run at. - * - Find the next minute to run at. - * - Find the next second to run at. - * - Check that the chosen time does not equal the current execution. - * - Return the selected date object. - */ - _getNextDateFrom: function (start, zone) { - if (start instanceof Date) { - start = luxon.DateTime.fromJSDate(start); - } - let date = start; - const firstDate = start.toMillis(); - if (zone) { - date = date.setZone(zone); - } - if (!this.realDate) { - if (date.millisecond > 0) { - date = date.set({ millisecond: 0, second: date.second + 1 }); - } - } - - if (date.invalid) { - throw new Error('ERROR: You specified an invalid date.'); - } - - /** - * maximum match interval is 8 years: - * crontab has '* * 29 2 *' and we are on 1 March 2096: - * next matching time will be 29 February 2104 - * source: https://github.com/cronie-crond/cronie/blob/0d669551680f733a4bdd6bab082a0b3d6d7f089c/src/cronnext.c#L401-L403 - */ - const maxMatch = luxon.DateTime.now().plus({ years: 8 }); - - // determine next date - while (true) { - const diff = date - start; - - // hard stop if the current date is after the maximum match interval - if (date > maxMatch) { - throw new Error( - `Something went wrong. No execution date was found in the next 8 years. - Please provide the following string if you would like to help debug: - Time Zone: ${zone || '""'} - Cron String: ${this} - UTC offset: ${date.offset} - - current Date: ${luxon.DateTime.local().toString()}` - ); - } - - if ( - !(date.month in this.month) && - Object.keys(this.month).length !== 12 - ) { - date = date.plus({ months: 1 }); - date = date.set({ day: 1, hour: 0, minute: 0, second: 0 }); - - if (this._forwardDSTJump(0, 0, date)) { - const [done, newDate] = this._findPreviousDSTJump(date); - date = newDate; - if (done) break; - } - continue; - } - - if ( - !(date.day in this.dayOfMonth) && - Object.keys(this.dayOfMonth).length !== 31 && - !( - date.getWeekDay() in this.dayOfWeek && - Object.keys(this.dayOfWeek).length !== 7 - ) - ) { - date = date.plus({ days: 1 }); - date = date.set({ hour: 0, minute: 0, second: 0 }); - - if (this._forwardDSTJump(0, 0, date)) { - const [done, newDate] = this._findPreviousDSTJump(date); - date = newDate; - if (done) break; - } - continue; - } - - if ( - !(date.getWeekDay() in this.dayOfWeek) && - Object.keys(this.dayOfWeek).length !== 7 && - !( - date.day in this.dayOfMonth && - Object.keys(this.dayOfMonth).length !== 31 - ) - ) { - date = date.plus({ days: 1 }); - date = date.set({ hour: 0, minute: 0, second: 0 }); - if (this._forwardDSTJump(0, 0, date)) { - const [done, newDate] = this._findPreviousDSTJump(date); - date = newDate; - if (done) break; - } - continue; - } - - if (!(date.hour in this.hour) && Object.keys(this.hour).length !== 24) { - const expectedHour = - date.hour === 23 && diff > 86400000 ? 0 : date.hour + 1; - const expectedMinute = date.minute; // expect no change. - - date = date.set({ hour: expectedHour }); - date = date.set({ minute: 0, second: 0 }); - - // When this is the case, Asking luxon to go forward by 1 hour actually made us go forward by more hours... - // This indicates that somewhere between these two time points, a forward DST adjustment has happened. - // When this happens, the job should be scheduled to execute as though the time has come when the jump is made. - // Therefore, the job should be scheduled on the first tick after the forward jump. - if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { - const [done, newDate] = this._findPreviousDSTJump(date); - date = newDate; - if (done) break; - } - // backwards jumps do not seem to have any problems (i.e. double activations), - // so they need not be handled in a similar way. - - continue; - } - - if ( - !(date.minute in this.minute) && - Object.keys(this.minute).length !== 60 - ) { - const expectedMinute = - date.minute === 59 && diff > 3600000 ? 0 : date.minute + 1; - const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0); - - date = date.set({ minute: expectedMinute }); - date = date.set({ second: 0 }); - - // Same case as with hours: DST forward jump. - // This must be accounted for if a minute increment pushed us to a jumping point. - if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { - const [done, newDate] = this._findPreviousDSTJump(date); - date = newDate; - if (done) break; - } - - continue; - } - - if ( - !(date.second in this.second) && - Object.keys(this.second).length !== 60 - ) { - const expectedSecond = - date.second === 59 && diff > 60000 ? 0 : date.second + 1; - const expectedMinute = date.minute + (expectedSecond === 60); - const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0); - - date = date.set({ second: expectedSecond }); - - // Seconds can cause it too, imagine 21:59:59 -> 23:00:00. - if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { - const [done, newDate] = this._findPreviousDSTJump(date); - date = newDate; - if (done) break; - } - - continue; - } - - if (date.toMillis() === firstDate) { - const expectedSecond = date.second + 1; - const expectedMinute = date.minute + (expectedSecond === 60); - const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0); - - date = date.set({ second: expectedSecond }); - - // Same as always. - if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { - const [done, newDate] = this._findPreviousDSTJump(date); - date = newDate; - if (done) break; - } - - continue; - } - - break; - } - - return date; - }, - - /** - * Search backwards in time 1 minute at a time, to detect a DST forward jump. - * When the jump is found, the range of the jump is investigated to check for acceptable cron times. - * - * A pair is returned, whose first is a boolean representing if an acceptable time was found inside the jump, - * and whose second is a DateTime representing the first millisecond after the jump. - * - * The input date is expected to be decently close to a DST jump. - * Up to a day in the past is checked before an error is thrown. - * @param date - * @return [boolean, DateTime] - */ - _findPreviousDSTJump: function (date) { - /** @type number */ - let expectedMinute, expectedHour, actualMinute, actualHour; - /** @type DateTime */ - let maybeJumpingPoint = date; - - // representing one day of backwards checking. If this is hit, the input must be wrong. - const iterationLimit = 60 * 24; - let iteration = 0; - do { - if (++iteration > iterationLimit) { - throw new Error( - `ERROR: This DST checking related function assumes the input DateTime (${date.toISO()}) is within 24 hours of a DST jump.` - ); - } - - expectedMinute = maybeJumpingPoint.minute - 1; - expectedHour = maybeJumpingPoint.hour; - - if (expectedMinute < 0) { - expectedMinute += 60; - expectedHour = (expectedHour + 24 - 1) % 24; // Subtract 1 hour, but we must account for the -1 case. - } - - maybeJumpingPoint = maybeJumpingPoint.minus({ minute: 1 }); - - actualMinute = maybeJumpingPoint.minute; - actualHour = maybeJumpingPoint.hour; - } while (expectedMinute === actualMinute && expectedHour === actualHour); - - // Setting the seconds and milliseconds to zero is necessary for two reasons: - // Firstly, the range checking function needs the earliest moment after the jump. - // Secondly, this DateTime may be used for scheduling jobs, if there existed a job in the skipped range. - const afterJumpingPoint = maybeJumpingPoint - .plus({ minute: 1 }) // back to the first minute _after_ the jump - .set({ seconds: 0, millisecond: 0 }); - - // Get the lower bound of the range to check as well. This only has to be accurate down to minutes. - const beforeJumpingPoint = afterJumpingPoint.minus({ second: 1 }); - - if ( - date.month + 1 in this.month && - date.day in this.dayOfMonth && - date.getWeekDay() in this.dayOfWeek - ) { - return [ - this._checkTimeInSkippedRange(beforeJumpingPoint, afterJumpingPoint), - afterJumpingPoint - ]; - } - - // no valid time in the range for sure, units that didn't change from the skip mismatch. - return [false, afterJumpingPoint]; - }, - - /** - * Given 2 DateTimes, which represent 1 second before and immediately after a DST forward jump, - * checks if a time in the skipped range would have been a valid CronJob time. - * - * Could technically work with just one of these values, extracting the other by adding or subtracting seconds. - * However, this couples the input DateTime to actually being tied to a DST jump, - * which would make the function harder to test. - * This way the logic just tests a range of minutes and hours, regardless if there are skipped time points underneath. - * - * Assumes the DST jump started no earlier than 0:00 and jumped forward by at least 1 minute, to at most 23:59. - * i.e. The day is assumed constant, but the jump is not assumed to be an hour long. - * Empirically, it is almost always one hour, but very, very rarely 30 minutes. - * - * Assumes dayOfWeek, dayOfMonth and month match all match, so only the hours, minutes and seconds are to be checked. - * @param {DateTime} beforeJumpingPoint - * @param {DateTime} afterJumpingPoint - * @returns {boolean} - */ - _checkTimeInSkippedRange: function (beforeJumpingPoint, afterJumpingPoint) { - // start by getting the first minute & hour inside the skipped range. - const startingMinute = (beforeJumpingPoint.minute + 1) % 60; - const startingHour = - (beforeJumpingPoint.hour + (startingMinute === 0)) % 24; - - const hourRangeSize = afterJumpingPoint.hour - startingHour + 1; - const isHourJump = startingMinute === 0 && afterJumpingPoint.minute === 0; - - // There exist DST jumps other than 1 hour long, and the function is built to deal with it. - // It may be overkill to assume some cases, but it shouldn't cost much at runtime. - // https://en.wikipedia.org/wiki/Daylight_saving_time_by_country - if (hourRangeSize === 2 && isHourJump) { - // Exact 1 hour jump, most common real-world case. - // There is no need to check minutes and seconds, as any value would suffice. - return startingHour in this.hour; - } else if (hourRangeSize === 1) { - // less than 1 hour jump, rare but does exist. - return ( - startingHour in this.hour && - this._checkTimeInSkippedRangeSingleHour( - startingMinute, - afterJumpingPoint.minute - ) - ); - } else { - // non-round or multi-hour jump. (does not exist in the real world at the time of writing) - return this._checkTimeInSkippedRangeMultiHour( - startingHour, - startingMinute, - afterJumpingPoint.hour, - afterJumpingPoint.minute - ); - } - }, - - /** - * Component of checking if a CronJob time existed in a DateTime range skipped by DST. - * This subroutine makes a further assumption that the skipped range is fully contained in one hour, - * and that all other larger units are valid for the job. - * - * for example a jump from 02:00:00 to 02:30:00, but not from 02:00:00 to 03:00:00. - * @see _checkTimeInSkippedRange - * - * This is done by checking if any minute in startMinute - endMinute is valid, excluding endMinute. - * For endMinute, there is only a match if the 0th second is a valid time. - */ - _checkTimeInSkippedRangeSingleHour: function (startMinute, endMinute) { - for (let minute = startMinute; minute < endMinute; ++minute) { - if (minute in this.minute) return true; - } - - // Unless the very last second of the jump matched, there is no match. - return endMinute in this.minute && 0 in this.second; - }, - - /** - * Component of checking if a CronJob time existed in a DateTime range skipped by DST. - * This subroutine assumes the jump touches at least 2 hours, but the jump does not necessarily fully contain these hours. - * - * @see _checkTimeInSkippedRange - * - * This is done by defining the minutes to check for the first and last hour, - * and checking all 60 minutes for any hours in between them. - * - * If any hour x minute combination is a valid time, true is returned. - * The endMinute x endHour combination is only checked with the 0th second, since the rest would be out of the range. - * - * @param startHour {number} - * @param startMinute {number} - * @param endHour {number} - * @param endMinute {number} - */ - _checkTimeInSkippedRangeMultiHour: function ( - startHour, - startMinute, - endHour, - endMinute - ) { - if (startHour >= endHour) { - throw new Error( - `ERROR: This DST checking related function assumes the forward jump starting hour (${startHour}) is less than the end hour (${endHour})` - ); - } - - /** @type number[] */ - const firstHourMinuteRange = Array.from( - { length: 60 - startMinute }, - (_, k) => startMinute + k - ); - /** @type {number[]} The final minute is not contained on purpose. Every minute in this range represents one for which any second is valid. */ - const lastHourMinuteRange = Array.from( - { length: endMinute }, - (_, k) => k - ); - /** @type number[] */ - const middleHourMinuteRange = Array.from({ length: 60 }, (_, k) => k); - - /** @type (number) => number[] */ - const selectRange = forHour => { - if (forHour === startHour) { - return firstHourMinuteRange; - } else if (forHour === endHour) { - return lastHourMinuteRange; - } else { - return middleHourMinuteRange; - } - }; - - // Include the endHour: Selecting the right range still ensures no values outside the skip are checked. - for (let hour = startHour; hour <= endHour; ++hour) { - if (!(hour in this.hour)) continue; - - // The hour matches, so if the minute is in the range, we have a match! - const usingRange = selectRange(hour); - - for (const minute of usingRange) { - // All minutes in any of the selected ranges represent minutes which are fully contained in the jump, - // So we need not check the seconds. If the minute is in there, it is a match. - if (minute in this.minute) return true; - } - } - - // The endMinute of the endHour was not checked in the loop, because only the 0th second of it is in the range. - // Arriving here means no match was found yet, but this final check may turn up as a match. - return ( - endHour in this.hour && endMinute in this.minute && 0 in this.second - ); - }, - /** - * Given expected and actual hours and minutes, report if a DST forward jump occurred. - * - * This is the case when the expected is smaller than the acutal. - * - * It is not sufficient to check only hours, because some parts of the world apply DST by shifting in minutes. - * Better to account for it by checking minutes too, before an Australian of Lord Howe Island call us. - * @param expectedHour - * @param expectedMinute - * @param {DateTime} actualDate - */ - _forwardDSTJump: function (expectedHour, expectedMinute, actualDate) { - const actualHour = actualDate.hour; - const actualMinute = actualDate.minute; - - const hoursJumped = expectedHour % 24 < actualHour; - const minutesJumped = expectedMinute % 60 < actualMinute; - - return hoursJumped || minutesJumped; - }, - - /** - * wildcard, or all params in array (for to string) - */ - _wcOrAll: function (type) { - if (this._hasAll(type)) { - return '*'; - } - - const all = []; - for (const time in this[type]) { - all.push(time); - } - - return all.join(','); - }, - - _hasAll: function (type) { - const constraints = CONSTRAINTS[TIME_UNITS.indexOf(type)]; - const low = constraints[0]; - const high = - type === TIME_UNITS_MAP.DAY_OF_WEEK - ? constraints[1] - 1 - : constraints[1]; - - for (let i = low, n = high; i < n; i++) { - if (!(i in this[type])) { - return false; - } - } - - return true; - }, - - /* - * Parse the cron syntax into something useful for selecting the next execution time. - * - * Algorithm: - * - Replace preset - * - Replace aliases in the source. - * - Trim string and split for processing. - * - Loop over split options (ms -> month): - * - Get the value (or default) in the current position. - * - Parse the value. - */ - _parse: function (source) { - source = source.toLowerCase(); - - if (source in PRESETS) { - source = PRESETS[source]; - } - - source = source.replace(/[a-z]{1,3}/gi, alias => { - if (alias in ALIASES) { - return ALIASES[alias]; - } - - throw new Error(`Unknown alias: ${alias}`); - }); - - const units = source.trim().split(/\s+/); - - // seconds are optional - if (units.length < TIME_UNITS_LEN - 1) { - throw new Error('Too few fields'); - } - - if (units.length > TIME_UNITS_LEN) { - throw new Error('Too many fields'); - } - - const unitsLen = units.length; - for (let i = 0; i < TIME_UNITS_LEN; i++) { - // If the split source string doesn't contain all digits, - // assume defaults for first n missing digits. - // This adds support for 5-digit standard cron syntax - const cur = units[i - (TIME_UNITS_LEN - unitsLen)] || PARSE_DEFAULTS[i]; - this._parseField(cur, TIME_UNITS[i], CONSTRAINTS[i]); - } - }, - - /* - * Parse individual field from the cron syntax provided. - * - * Algorithm: - * - Split field by commas aand check for wildcards to ensure proper user. - * - Replace wildcard values with - boundaries. - * - Split field by commas and then iterate over ranges inside field. - * - If range matches pattern then map over matches using replace (to parse the range by the regex pattern) - * - Starting with the lower bounds of the range iterate by step up to the upper bounds and toggle the CronTime field value flag on. - */ - _parseField: function (value, type, constraints) { - const typeObj = this[type]; - let pointer; - const low = constraints[0]; - const high = constraints[1]; - - const fields = value.split(','); - fields.forEach(field => { - const wildcardIndex = field.indexOf('*'); - if (wildcardIndex !== -1 && wildcardIndex !== 0) { - throw new Error( - `Field (${field}) has an invalid wildcard expression` - ); - } - }); - - // * is a shortcut to [low-high] range for the field - value = value.replace(RE_WILDCARDS, `${low}-${high}`); - - // commas separate information, so split based on those - const allRanges = value.split(','); - - for (let i = 0; i < allRanges.length; i++) { - if (allRanges[i].match(RE_RANGE)) { - allRanges[i].replace(RE_RANGE, ($0, lower, upper, step) => { - lower = parseInt(lower, 10); - upper = upper !== undefined ? parseInt(upper, 10) : undefined; - - const wasStepDefined = !isNaN(parseInt(step, 10)); - if (step === '0') { - throw new Error(`Field (${type}) has a step of zero`); - } - step = parseInt(step, 10) || 1; - - if (upper !== undefined && lower > upper) { - throw new Error(`Field (${type}) has an invalid range`); - } - - const outOfRangeError = - lower < low || - (upper !== undefined && upper > high) || - (upper === undefined && lower > high); - - if (outOfRangeError) { - throw new Error(`Field value (${value}) is out of range`); - } - - // Positive integer higher than constraints[0] - lower = Math.min(Math.max(low, ~~Math.abs(lower)), high); - - // Positive integer lower than constraints[1] - if (upper !== undefined) { - upper = Math.min(high, ~~Math.abs(upper)); - } else { - // If step is provided, the default upper range is the highest value - upper = wasStepDefined ? high : lower; - } - - // Count from the lower barrier to the upper - pointer = lower; - - do { - typeObj[pointer] = true; // mutates the field objects values inside CronTime - pointer += step; - } while (pointer <= upper); - - // merge day 7 into day 0 (both Sunday), and remove day 7 - // since we work with day-of-week 0-6 under the hood - if (type === 'dayOfWeek') { - if (!typeObj[0] && !!typeObj[7]) typeObj[0] = typeObj[7]; - delete typeObj[7]; - } - }); - } else { - throw new Error(`Field (${type}) cannot be parsed`); - } - } - } - }; - - return CT; -} - -module.exports = CronTime; diff --git a/package-lock.json b/package-lock.json index aab51cfd..a2a834b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,21 +22,22 @@ "@semantic-release/github": "~8.1.x", "@semantic-release/npm": "~10.0.x", "@semantic-release/release-notes-generator": "~11.0.x", + "@types/jest": "^29.5.5", + "@types/node": "^20.6.0", + "@types/sinon": "^10.0.16", + "@typescript-eslint/eslint-plugin": "^5.62.0", "chai": "~4.2.x", "eslint": "~8.36.x", "eslint-config-prettier": "^8.7.x", - "eslint-config-standard": "~17.0.x", - "eslint-plugin-import": "~2.27.x", - "eslint-plugin-jest": "~27.2.x", - "eslint-plugin-n": "~15.6.x", + "eslint-plugin-jest": "^27.4.0", "eslint-plugin-prettier": "~4.2.x", - "eslint-plugin-promise": "~6.1.x", "husky": "^8.0.3", "jest": "~29.5.x", "prettier": "~2.8.x", "semantic-release": "~21.0.x", "sinon": "^15.0.x", - "tsd": "^0.28.1" + "ts-jest": "^29.1.1", + "typescript": "~5.1.6" } }, "node_modules/@ampproject/remapping": { @@ -1002,9 +1003,10 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.4.0", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2128,27 +2130,27 @@ "dev": true }, "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==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@sinonjs/samsam": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", - "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "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", @@ -2156,6 +2158,15 @@ "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", @@ -2186,12 +2197,6 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, - "node_modules/@tsd/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-YQi2lvZSI+xidKeUjlbv6b6Zw7qB3aXHw5oGJLs5OOGAEqKIOvz5UIAkWyg0bJbkSUWPBEtaOHpVxU4EYBO1Jg==", - "dev": true - }, "node_modules/@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -2233,22 +2238,6 @@ "@babel/types": "^7.3.0" } }, - "node_modules/@types/eslint": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", - "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -2282,13 +2271,18 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/json-schema": { - "version": "7.0.11", + "node_modules/@types/jest": { + "version": "29.5.5", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", + "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", "dev": true, - "license": "MIT" + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } }, - "node_modules/@types/json5": { - "version": "0.0.29", + "node_modules/@types/json-schema": { + "version": "7.0.11", "dev": true, "license": "MIT" }, @@ -2304,9 +2298,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", - "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", + "version": "20.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", + "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==", "dev": true }, "node_modules/@types/normalize-package-data": { @@ -2326,6 +2320,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/sinon": { + "version": "10.0.16", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.16.tgz", + "integrity": "sha512-j2Du5SYpXZjJVJtXBokASpPRj+e2z+VUhCPHmM6WMfe3dpHu6iVKJMU6AiBcMp/XTAYnEj6Wc1trJUWwZ0QaAQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -2347,13 +2356,110 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2361,12 +2467,21 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/types": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2376,12 +2491,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2402,9 +2518,10 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -2416,16 +2533,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -2475,11 +2593,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -2491,11 +2610,15 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.3.0", + "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, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/acorn": { @@ -2656,42 +2779,12 @@ "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", "dev": true }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "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/array-includes": { - "version": "3.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-union": { "version": "2.1.0", "dev": true, @@ -2700,40 +2793,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2751,17 +2810,6 @@ "node": "*" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-jest": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", @@ -2918,6 +2966,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -2933,40 +2993,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/builtins": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/builtins/node_modules/semver": { - "version": "7.3.8", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "dev": true, @@ -3496,21 +3522,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-properties": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -3743,110 +3754,26 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.21.2", + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint": { - "version": "8.36.0", + "node_modules/eslint": { + "version": "8.36.0", "dev": true, "license": "MIT", "dependencies": { @@ -3912,146 +3839,11 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-config-standard": { - "version": "17.0.0", - "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" - } - ], - "license": "MIT", - "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0", - "eslint-plugin-promise": "^6.0.0" - } - }, - "node_modules/eslint-formatter-pretty": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", - "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", - "dev": true, - "dependencies": { - "@types/eslint": "^7.2.13", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "eslint-rule-docs": "^1.1.5", - "log-symbols": "^4.0.0", - "plur": "^4.0.0", - "string-width": "^4.2.0", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-jest": { - "version": "27.2.1", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.0.tgz", + "integrity": "sha512-ukVeKmMPAUA5SWjHenvyyXnirKfHKMdOsTZdn5tZx5EW05HGVQwBohigjFZGGj3zuv1cV6hc82FvWv6LdIbkgg==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/utils": "^5.10.0" }, @@ -4059,8 +3851,9 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { @@ -4071,76 +3864,6 @@ } } }, - "node_modules/eslint-plugin-n": { - "version": "15.6.1", - "dev": true, - "license": "MIT", - "dependencies": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/eslint-plugin-es": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-n/node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.3.8", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-prettier": { "version": "4.2.1", "dev": true, @@ -4161,23 +3884,6 @@ } } }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-rule-docs": { - "version": "1.1.235", - "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz", - "integrity": "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==", - "dev": true - }, "node_modules/eslint-scope": { "version": "7.1.1", "dev": true, @@ -4190,39 +3896,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "1.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, "node_modules/eslint/node_modules/argparse": { "version": "2.0.1", "dev": true, @@ -4541,14 +4214,6 @@ "dev": true, "license": "ISC" }, - "node_modules/for-each": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.3" - } - }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -4578,50 +4243,11 @@ "dev": true, "license": "ISC" }, - "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", "dev": true, "license": "MIT" }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4648,19 +4274,6 @@ "node": "*" } }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -4682,21 +4295,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/git-log-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", @@ -4805,20 +4403,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/globby": { "version": "11.1.0", "dev": true, @@ -4838,17 +4422,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4860,6 +4433,12 @@ "dev": true, "license": "MIT" }, + "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", @@ -4901,14 +4480,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4918,53 +4489,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hook-std": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", @@ -5133,19 +4657,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "node_modules/internal-slot": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/into-stream": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", @@ -5162,71 +4673,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/irregular-plurals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", - "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-bigint": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.11.0", "dev": true, @@ -5238,20 +4690,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "dev": true, @@ -5289,17 +4727,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "dev": true, @@ -5308,20 +4735,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -5357,32 +4770,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5395,34 +4782,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", @@ -5435,24 +4794,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-unicode-supported": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", @@ -5465,17 +4806,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -6436,6 +5766,12 @@ "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", "dev": true }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "dev": true, @@ -6477,49 +5813,21 @@ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "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==", + "node_modules/lru-cache": { + "version": "6.0.0", "dev": true, + "license": "ISC", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "yallist": "^4.0.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/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/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/luxon": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", - "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "node_modules/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", "engines": { "node": ">=12" } @@ -6920,6 +6228,12 @@ "dev": true, "license": "MIT" }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -6945,6 +6259,15 @@ "path-to-regexp": "^1.7.0" } }, + "node_modules/nise/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/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -10351,55 +9674,6 @@ "inBundle": true, "license": "ISC" }, - "node_modules/object-inspect": { - "version": "1.12.3", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/once": { "version": "1.4.0", "dev": true, @@ -10789,21 +10063,6 @@ "node": ">=8" } }, - "node_modules/plur": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", - "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", - "dev": true, - "dependencies": { - "irregular-plurals": "^3.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -11270,33 +10529,6 @@ "esprima": "~4.0.0" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/registry-auth-token": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", @@ -11444,19 +10676,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/semantic-release": { "version": "21.0.2", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-21.0.2.tgz", @@ -11790,19 +11009,6 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -11907,14 +11113,14 @@ } }, "node_modules/sinon": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.2.tgz", - "integrity": "sha512-PCVP63XZkg0/LOqQH5rEU4LILuvTFMb5tNxTHfs6VUMNnZz2XrnGSTZbAGITjzwQWbl/Bl/8hi4G3zZWjyBwHg==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", + "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/samsam": "^7.0.1", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", "nise": "^5.1.4", "supports-color": "^7.2.0" @@ -11924,15 +11130,6 @@ "url": "https://opencollective.com/sinon" } }, - "node_modules/sinon/node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -12103,48 +11300,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "dev": true, @@ -12354,311 +11509,150 @@ "engines": { "node": ">=4" } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/traverse": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", - "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "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/tsconfig-paths": { - "version": "3.14.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsd": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.28.1.tgz", - "integrity": "sha512-FeYrfJ05QgEMW/qOukNCr4fAJHww4SaKnivAXRv4g5kj4FeLpNV7zH4dorzB9zAfVX4wmA7zWu/wQf7kkcvfbw==", - "dev": true, - "dependencies": { - "@tsd/typescript": "~5.0.2", - "eslint-formatter-pretty": "^4.1.0", - "globby": "^11.0.1", - "jest-diff": "^29.0.3", - "meow": "^9.0.0", - "path-exists": "^4.0.0", - "read-pkg-up": "^7.0.0" - }, - "bin": { - "tsd": "dist/cli.js" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/tsd/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/tsd/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/tsd/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/tsd/node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "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/tsd/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/tsd/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/tsd/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/tsd/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/tsd/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==", + }, + "node_modules/to-regex-range": { + "version": "5.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, "engines": { - "node": ">=8" + "node": ">=8.0" } }, - "node_modules/tsd/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==", + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/traverse": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", + "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", "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" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tsd/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==", + "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/tsd/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, "bin": { - "semver": "bin/semver" + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } } }, - "node_modules/tsd/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==", + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, "engines": { "node": ">=10" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "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" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "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/tsd/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "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": ">=10" + "node": ">=0.3.1" } }, "node_modules/tslib": { @@ -12710,29 +11704,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { - "version": "5.0.2", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/uglify-js": { @@ -12748,20 +11730,6 @@ "node": ">=0.8.0" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -12913,40 +11881,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/word-wrap": { "version": "1.2.3", "dev": true, @@ -13817,7 +12751,9 @@ } }, "@eslint-community/regexpp": { - "version": "4.4.0", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "dev": true }, "@eslint/eslintrc": { @@ -14642,32 +13578,43 @@ "dev": true }, "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "@sinonjs/samsam": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", - "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", "dev": true, "requires": { "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", "type-detect": "^4.0.8" + }, + "dependencies": { + "@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, + "requires": { + "type-detect": "4.0.8" + } + } } }, "@sinonjs/text-encoding": { @@ -14700,12 +13647,6 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, - "@tsd/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-YQi2lvZSI+xidKeUjlbv6b6Zw7qB3aXHw5oGJLs5OOGAEqKIOvz5UIAkWyg0bJbkSUWPBEtaOHpVxU4EYBO1Jg==", - "dev": true - }, "@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -14747,22 +13688,6 @@ "@babel/types": "^7.3.0" } }, - "@types/eslint": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", - "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, "@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -14796,14 +13721,20 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "29.5.5", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", + "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "@types/json-schema": { "version": "7.0.11", "dev": true }, - "@types/json5": { - "version": "0.0.29", - "dev": true - }, "@types/luxon": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.1.tgz", @@ -14816,9 +13747,9 @@ "dev": true }, "@types/node": { - "version": "18.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", - "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", + "version": "20.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", + "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==", "dev": true }, "@types/normalize-package-data": { @@ -14837,6 +13768,21 @@ "version": "7.3.13", "dev": true }, + "@types/sinon": { + "version": "10.0.16", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.16.tgz", + "integrity": "sha512-j2Du5SYpXZjJVJtXBokASpPRj+e2z+VUhCPHmM6WMfe3dpHu6iVKJMU6AiBcMp/XTAYnEj6Wc1trJUWwZ0QaAQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -14858,24 +13804,84 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "peer": true, + "requires": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + } + }, "@typescript-eslint/scope-manager": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -14884,7 +13890,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -14893,15 +13901,17 @@ } }, "@typescript-eslint/utils": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -14928,15 +13938,19 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.55.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.3.0", + "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 } } @@ -15052,55 +14066,16 @@ "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", "dev": true }, - "array-buffer-byte-length": { - "version": "1.0.0", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - } - }, "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 }, - "array-includes": { - "version": "3.1.6", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - } - }, "array-union": { "version": "2.1.0", "dev": true }, - "array.prototype.flat": { - "version": "1.3.1", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.1", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -15111,10 +14086,6 @@ "version": "1.1.0", "dev": true }, - "available-typed-arrays": { - "version": "1.0.5", - "dev": true - }, "babel-jest": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", @@ -15228,6 +14199,15 @@ "update-browserslist-db": "^1.0.10" } }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -15243,30 +14223,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "builtins": { - "version": "5.0.1", - "dev": true, - "requires": { - "semver": "^7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "call-bind": { - "version": "1.0.2", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, "callsites": { "version": "3.1.0", "dev": true @@ -15638,14 +14594,6 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, - "define-properties": { - "version": "1.2.0", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, "deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -15806,71 +14754,6 @@ "is-arrayish": "^0.2.1" } }, - "es-abstract": { - "version": "1.21.2", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - } - }, - "es-set-tostringtag": { - "version": "2.0.1", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -15955,145 +14838,15 @@ "dev": true, "requires": {} }, - "eslint-config-standard": { - "version": "17.0.0", - "dev": true, - "requires": {} - }, - "eslint-formatter-pretty": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", - "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", - "dev": true, - "requires": { - "@types/eslint": "^7.2.13", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "eslint-rule-docs": "^1.1.5", - "log-symbols": "^4.0.0", - "plur": "^4.0.0", - "string-width": "^4.2.0", - "supports-hyperlinks": "^2.0.0" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.7", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.27.5", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - } - } - }, "eslint-plugin-jest": { - "version": "27.2.1", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.0.tgz", + "integrity": "sha512-ukVeKmMPAUA5SWjHenvyyXnirKfHKMdOsTZdn5tZx5EW05HGVQwBohigjFZGGj3zuv1cV6hc82FvWv6LdIbkgg==", "dev": true, "requires": { "@typescript-eslint/utils": "^5.10.0" } }, - "eslint-plugin-n": { - "version": "15.6.1", - "dev": true, - "requires": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" - }, - "dependencies": { - "eslint-plugin-es": { - "version": "4.1.0", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - } - } - }, - "semver": { - "version": "7.3.8", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "eslint-plugin-prettier": { "version": "4.2.1", "dev": true, @@ -16101,17 +14854,6 @@ "prettier-linter-helpers": "^1.0.0" } }, - "eslint-plugin-promise": { - "version": "6.1.1", - "dev": true, - "requires": {} - }, - "eslint-rule-docs": { - "version": "1.1.235", - "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz", - "integrity": "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==", - "dev": true - }, "eslint-scope": { "version": "7.1.1", "dev": true, @@ -16120,23 +14862,6 @@ "estraverse": "^5.2.0" } }, - "eslint-utils": { - "version": "3.0.0", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "dev": true - }, "espree": { "version": "9.5.0", "dev": true, @@ -16329,13 +15054,6 @@ "version": "3.2.7", "dev": true }, - "for-each": { - "version": "0.3.3", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -16361,31 +15079,10 @@ "version": "1.0.0", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "dev": true }, - "function.prototype.name": { - "version": "1.1.5", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "dev": true - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -16402,15 +15099,6 @@ "version": "2.0.0", "dev": true }, - "get-intrinsic": { - "version": "1.2.0", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -16423,14 +15111,6 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, - "get-symbol-description": { - "version": "1.0.0", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, "git-log-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", @@ -16514,13 +15194,6 @@ "type-fest": "^0.20.2" } }, - "globalthis": { - "version": "1.0.3", - "dev": true, - "requires": { - "define-properties": "^1.1.3" - } - }, "globby": { "version": "11.1.0", "dev": true, @@ -16533,13 +15206,6 @@ "slash": "^3.0.0" } }, - "gopd": { - "version": "1.0.1", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -16550,6 +15216,12 @@ "version": "1.0.4", "dev": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -16576,38 +15248,12 @@ "function-bind": "^1.1.1" } }, - "has-bigints": { - "version": "1.0.2", - "dev": true - }, "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 }, - "has-property-descriptors": { - "version": "1.0.0", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, "hook-std": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", @@ -16717,15 +15363,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "internal-slot": { - "version": "1.0.5", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, "into-stream": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", @@ -16736,46 +15373,12 @@ "p-is-promise": "^3.0.0" } }, - "irregular-plurals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", - "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", - "dev": true - }, - "is-array-buffer": { - "version": "3.0.2", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "is-bigint": { - "version": "1.0.4", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "dev": true - }, "is-core-module": { "version": "2.11.0", "dev": true, @@ -16783,13 +15386,6 @@ "has": "^1.0.3" } }, - "is-date-object": { - "version": "1.0.5", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, "is-extglob": { "version": "2.1.1", "dev": true @@ -16813,21 +15409,10 @@ "is-extglob": "^2.1.1" } }, - "is-negative-zero": { - "version": "2.0.2", - "dev": true - }, "is-number": { "version": "7.0.0", "dev": true }, - "is-number-object": { - "version": "1.0.7", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -16850,41 +15435,12 @@ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true }, - "is-regex": { - "version": "1.1.4", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, "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 }, - "is-string": { - "version": "1.0.7", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", @@ -16894,30 +15450,12 @@ "text-extensions": "^1.0.0" } }, - "is-typed-array": { - "version": "1.1.10", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, "is-unicode-supported": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", "dev": true }, - "is-weakref": { - "version": "1.0.2", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -17665,6 +16203,12 @@ "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", "dev": true }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "dev": true @@ -17705,24 +16249,6 @@ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true }, - "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, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "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 - } - } - }, "lru-cache": { "version": "6.0.0", "dev": true, @@ -18013,6 +16539,12 @@ "version": "1.4.0", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -18036,6 +16568,17 @@ "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@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, + "requires": { + "type-detect": "4.0.8" + } + } } }, "node-emoji": { @@ -20340,33 +18883,6 @@ "path-key": "^3.0.0" } }, - "object-inspect": { - "version": "1.12.3", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.values": { - "version": "1.1.6", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, "once": { "version": "1.4.0", "dev": true, @@ -20631,15 +19147,6 @@ } } }, - "plur": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", - "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", - "dev": true, - "requires": { - "irregular-plurals": "^3.2.0" - } - }, "prelude-ls": { "version": "1.2.1", "dev": true @@ -20953,19 +19460,6 @@ "esprima": "~4.0.0" } }, - "regexp.prototype.flags": { - "version": "1.4.3", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "dev": true - }, "registry-auth-token": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", @@ -21056,15 +19550,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "safe-regex-test": { - "version": "1.0.0", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, "semantic-release": { "version": "21.0.2", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-21.0.2.tgz", @@ -21278,15 +19763,6 @@ "version": "3.0.0", "dev": true }, - "side-channel": { - "version": "1.0.4", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -21372,28 +19848,17 @@ } }, "sinon": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.2.tgz", - "integrity": "sha512-PCVP63XZkg0/LOqQH5rEU4LILuvTFMb5tNxTHfs6VUMNnZz2XrnGSTZbAGITjzwQWbl/Bl/8hi4G3zZWjyBwHg==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", + "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/samsam": "^7.0.1", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", "nise": "^5.1.4", "supports-color": "^7.2.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } } }, "sisteransi": { @@ -21546,33 +20011,6 @@ "strip-ansi": "^6.0.1" } }, - "string.prototype.trim": { - "version": "1.2.7", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.6", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimstart": { - "version": "1.0.6", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, "strip-ansi": { "version": "6.0.1", "dev": true, @@ -21744,6 +20182,33 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -21773,174 +20238,6 @@ } } }, - "tsconfig-paths": { - "version": "3.14.2", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.2", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } - } - }, - "tsd": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.28.1.tgz", - "integrity": "sha512-FeYrfJ05QgEMW/qOukNCr4fAJHww4SaKnivAXRv4g5kj4FeLpNV7zH4dorzB9zAfVX4wmA7zWu/wQf7kkcvfbw==", - "dev": true, - "requires": { - "@tsd/typescript": "~5.0.2", - "eslint-formatter-pretty": "^4.1.0", - "globby": "^11.0.1", - "jest-diff": "^29.0.3", - "meow": "^9.0.0", - "path-exists": "^4.0.0", - "read-pkg-up": "^7.0.0" - }, - "dependencies": { - "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, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "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 - }, - "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, - "requires": { - "p-locate": "^4.1.0" - } - }, - "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "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" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "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, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "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, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "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 - } - } - }, - "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, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "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 - } - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "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 - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } - }, "tslib": { "version": "1.14.1", "dev": true @@ -21967,17 +20264,10 @@ "version": "0.20.2", "dev": true }, - "typed-array-length": { - "version": "1.0.4", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - } - }, "typescript": { - "version": "5.0.2", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true }, "uglify-js": { @@ -21987,16 +20277,6 @@ "dev": true, "optional": true }, - "unbox-primitive": { - "version": "1.0.2", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, "unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -22114,29 +20394,6 @@ "isexe": "^2.0.0" } }, - "which-boxed-primitive": { - "version": "1.0.2", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-typed-array": { - "version": "1.1.9", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, "word-wrap": { "version": "1.2.3", "dev": true diff --git a/package.json b/package.json index bd8710a0..8b4eb214 100644 --- a/package.json +++ b/package.json @@ -10,16 +10,18 @@ "type": "git", "url": "https://github.com/kelektiv/node-cron.git" }, - "main": "lib/cron", + "main": "dist/index", "scripts": { - "lint": "eslint {lib,tests}/*.js", + "build": "tsc -b tsconfig.build.json", + "lint:eslint": "eslint src/ tests/ --ext .ts", + "lint:prettier": "prettier ./**/*.{json,md,yml} --check", + "lint": "npm run lint:eslint && npm run lint:prettier", + "lint:fix": "npm run lint:eslint -- --fix && npm run lint:prettier -- --write", "test": "jest --coverage", "test:watch": "jest --watch --coverage", - "test:types": "tsd", "prepare": "husky install", "release": "semantic-release" }, - "types": "types/index.d.ts", "dependencies": { "@types/luxon": "~3.3.0", "luxon": "~3.3.0" @@ -34,21 +36,22 @@ "@semantic-release/github": "~8.1.x", "@semantic-release/npm": "~10.0.x", "@semantic-release/release-notes-generator": "~11.0.x", + "@types/jest": "^29.5.5", + "@types/node": "^20.6.0", + "@types/sinon": "^10.0.16", + "@typescript-eslint/eslint-plugin": "^5.62.0", "chai": "~4.2.x", "eslint": "~8.36.x", "eslint-config-prettier": "^8.7.x", - "eslint-config-standard": "~17.0.x", - "eslint-plugin-import": "~2.27.x", - "eslint-plugin-jest": "~27.2.x", - "eslint-plugin-n": "~15.6.x", + "eslint-plugin-jest": "^27.4.0", "eslint-plugin-prettier": "~4.2.x", - "eslint-plugin-promise": "~6.1.x", "husky": "^8.0.3", "jest": "~29.5.x", "prettier": "~2.8.x", "semantic-release": "~21.0.x", "sinon": "^15.0.x", - "tsd": "^0.28.1" + "ts-jest": "^29.1.1", + "typescript": "~5.1.6" }, "keywords": [ "cron", @@ -77,23 +80,9 @@ "Masakazu Matsushita (matsukaz)", "Christopher Lunt (https://github.com/kirisu)" ], - "jest": { - "collectCoverage": true, - "collectCoverageFrom": [ - "lib/*.js" - ], - "coverageThreshold": { - "global": { - "statements": 80, - "branches": 80, - "functions": 70, - "lines": 80 - } - } - }, "files": [ - "lib", - "types", + "dist/**/*.js", + "dist/**/*.d.ts", "CHANGELOG.md", "LICENSE", "README.md" diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..0b6856cf --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,81 @@ +export const CONSTRAINTS = Object.freeze({ + second: [0, 59], + minute: [0, 59], + hour: [0, 23], + dayOfMonth: [1, 31], + month: [1, 12], + dayOfWeek: [0, 7] +} as const); +export const MONTH_CONSTRAINTS = Object.freeze({ + 1: 31, + 2: 29, // support leap year...not perfect + 3: 31, + 4: 30, + 5: 31, + 6: 30, + 7: 31, + 8: 31, + 9: 30, + 10: 31, + 11: 30, + 12: 31 +} as const); +export const PARSE_DEFAULTS = Object.freeze({ + second: '0', + minute: '*', + hour: '*', + dayOfMonth: '*', + month: '*', + dayOfWeek: '*' +} as const); +export const ALIASES = Object.freeze({ + jan: 1, + feb: 2, + mar: 3, + apr: 4, + may: 5, + jun: 6, + jul: 7, + aug: 8, + sep: 9, + oct: 10, + nov: 11, + dec: 12, + sun: 0, + mon: 1, + tue: 2, + wed: 3, + thu: 4, + fri: 5, + sat: 6 +} as const); +export const TIME_UNITS_MAP = Object.freeze({ + SECOND: 'second', + MINUTE: 'minute', + HOUR: 'hour', + DAY_OF_MONTH: 'dayOfMonth', + MONTH: 'month', + DAY_OF_WEEK: 'dayOfWeek' +} as const); +export const TIME_UNITS = Object.freeze(Object.values(TIME_UNITS_MAP)) as [ + 'second', + 'minute', + 'hour', + 'dayOfMonth', + 'month', + 'dayOfWeek' +]; +export const TIME_UNITS_LEN: number = TIME_UNITS.length; +export const PRESETS = Object.freeze({ + '@yearly': '0 0 0 1 1 *', + '@monthly': '0 0 0 1 * *', + '@weekly': '0 0 0 * * 0', + '@daily': '0 0 0 * * *', + '@hourly': '0 0 * * * *', + '@minutely': '0 * * * * *', + '@secondly': '* * * * * *', + '@weekdays': '0 0 0 * * 1-5', + '@weekends': '0 0 0 * * 0,6' +} as const); +export const RE_WILDCARDS = /\*/g; +export const RE_RANGE = /^(\d+)(?:-(\d+))?(?:\/(\d+))?$/g; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..e3d36e93 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,11 @@ +import { DateTime } from 'luxon'; +import { CronTime } from './time'; + +export { CronJob } from './job'; +export { CronTime } from './time'; + +export const sendAt = (cronTime: string | Date | DateTime): DateTime => + new CronTime(cronTime).sendAt(); + +export const timeout = (cronTime: string | Date | DateTime): number => + new CronTime(cronTime).getTimeout(); diff --git a/src/job.ts b/src/job.ts new file mode 100644 index 00000000..1d04c8e6 --- /dev/null +++ b/src/job.ts @@ -0,0 +1,215 @@ +import { spawn } from 'child_process'; +import { CronTime } from './time'; +import { CronCommand, CronJobParams } from './types/cron.types'; + +export class CronJob { + cronTime: CronTime; + running = false; + unrefTimeout = false; + lastExecution: Date | null = null; + runOnce = false; + context: unknown; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onComplete?: (...args: any) => void; + + private _timeout?: NodeJS.Timeout; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _callbacks: ((...args: any) => void)[] = []; + + constructor( + cronTime: CronJobParams['cronTime'], + onTick: CronJobParams['onTick'], + onComplete?: CronJobParams['onComplete'], + start?: CronJobParams['start'], + timeZone?: CronJobParams['timeZone'], + context?: CronJobParams['context'], + runOnInit?: CronJobParams['runOnInit'], + utcOffset?: CronJobParams['utcOffset'], + unrefTimeout?: CronJobParams['unrefTimeout'] + ) { + this.context = context || this; + this.cronTime = new CronTime(cronTime, timeZone, utcOffset); + + if (unrefTimeout != null) { + this.unrefTimeout = unrefTimeout; + } + + if (onComplete != null) { + this.onComplete = this._fnWrap(onComplete); + } + + if (this.cronTime.realDate) { + this.runOnce = true; + } + + this.addCallback(this._fnWrap(onTick)); + + if (runOnInit) { + this.lastExecution = new Date(); + this.fireOnTick(); + } + + if (start) this.start(); + } + + static from(params: CronJobParams) { + return new CronJob( + params.cronTime, + params.onTick, + params.onComplete, + params.start, + params.timeZone, + params.context, + params.runOnInit, + params.utcOffset, + params.unrefTimeout + ); + } + + private _fnWrap(cmd: CronCommand | string) { + switch (typeof cmd) { + case 'function': { + return cmd; + } + + case 'string': { + const [command, ...args] = cmd.split(' '); + + return spawn.bind(undefined, command ?? cmd, args); + } + + case 'object': { + return spawn.bind( + undefined, + cmd.command, + cmd.args ?? [], + cmd.options ?? {} + ); + } + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + addCallback(callback: (...args: any) => void) { + if (typeof callback === 'function') { + this._callbacks.push(callback); + } + } + + setTime(time: CronTime) { + if (!(time instanceof CronTime)) { + throw new Error('time must be an instance of CronTime.'); + } + const wasRunning = this.running; + this.stop(); + this.cronTime = time; + if (wasRunning) this.start(); + } + + nextDate() { + return this.cronTime.sendAt(); + } + + fireOnTick() { + for (const callback of this._callbacks) { + callback.call(this.context, this.onComplete); + } + } + + nextDates(i?: number) { + return this.cronTime.sendAt(i ?? 0); + } + + start() { + if (this.running) { + return; + } + + const MAXDELAY = 2147483647; // The maximum number of milliseconds setTimeout will wait. + let timeout = this.cronTime.getTimeout(); + let remaining = 0; + let startTime: number; + + const setCronTimeout = (t: number) => { + startTime = Date.now(); + this._timeout = setTimeout(callbackWrapper, t); + if (this.unrefTimeout && typeof this._timeout.unref === 'function') { + this._timeout.unref(); + } + }; + + // The callback wrapper checks if it needs to sleep another period or not + // and does the real callback logic when it's time. + const callbackWrapper = () => { + const diff = startTime + timeout - Date.now(); + + if (diff > 0) { + let newTimeout = this.cronTime.getTimeout(); + + if (newTimeout > diff) { + newTimeout = diff; + } + + remaining += newTimeout; + } + + // If there is sleep time remaining, calculate how long and go to sleep + // again. This processing might make us miss the deadline by a few ms + // times the number of sleep sessions. Given a MAXDELAY of almost a + // month, this should be no issue. + this.lastExecution = new Date(); + if (remaining) { + if (remaining > MAXDELAY) { + remaining -= MAXDELAY; + timeout = MAXDELAY; + } else { + timeout = remaining; + remaining = 0; + } + + setCronTimeout(timeout); + } else { + // We have arrived at the correct point in time. + + this.running = false; + + // start before calling back so the callbacks have the ability to stop the cron job + if (!this.runOnce) { + this.start(); + } + + this.fireOnTick(); + } + }; + + if (timeout >= 0) { + this.running = true; + + // Don't try to sleep more than MAXDELAY ms at a time. + + if (timeout > MAXDELAY) { + remaining = timeout - MAXDELAY; + timeout = MAXDELAY; + } + + setCronTimeout(timeout); + } else { + this.stop(); + } + } + + lastDate() { + return this.lastExecution; + } + + /** + * Stop the cronjob. + */ + stop() { + if (this._timeout) clearTimeout(this._timeout); + this.running = false; + if (typeof this.onComplete === 'function') { + this.onComplete(); + } + } +} diff --git a/src/time.ts b/src/time.ts new file mode 100644 index 00000000..be652eaa --- /dev/null +++ b/src/time.ts @@ -0,0 +1,825 @@ +import { DateTime, Zone } from 'luxon'; + +import { + ALIASES, + CONSTRAINTS, + MONTH_CONSTRAINTS, + PARSE_DEFAULTS, + PRESETS, + RE_RANGE, + RE_WILDCARDS, + TIME_UNITS, + TIME_UNITS_LEN, + TIME_UNITS_MAP +} from './constants'; +import { + DayOfMonthRange, + MonthRange, + Ranges, + TimeUnit, + TimeUnitField +} from './types/cron.types'; +import { getRecordKeys } from './utils'; + +export class CronTime { + source: string | DateTime; + zone?: string; + utcOffset?: number | string; + realDate = false; + + private second: TimeUnitField<'second'> = {}; + private minute: TimeUnitField<'minute'> = {}; + private hour: TimeUnitField<'hour'> = {}; + private dayOfMonth: TimeUnitField<'dayOfMonth'> = {}; + private month: TimeUnitField<'month'> = {}; + private dayOfWeek: TimeUnitField<'dayOfWeek'> = {}; + + constructor( + source: string | Date | DateTime, + zone?: string | null, + utcOffset?: string | number | null + ) { + if (zone) { + const dt = DateTime.fromObject({}, { zone }); + if (!dt.isValid) { + throw new Error('Invalid timezone.'); + } + + this.zone = zone; + } + + if (utcOffset != null) { + this.utcOffset = utcOffset; + } + + if (source instanceof Date || source instanceof DateTime) { + this.source = + source instanceof Date ? DateTime.fromJSDate(source) : source; + this.realDate = true; + } else { + this.source = source; + this._parse(this.source); + this._verifyParse(); + } + } + + private _getWeekDay(date: DateTime) { + return date.weekday === 7 ? 0 : date.weekday; + } + + /* + * Ensure that the syntax parsed correctly and correct the specified values if needed. + */ + private _verifyParse() { + const months = getRecordKeys(this.month); + const daysOfMonth = getRecordKeys(this.dayOfMonth); + + let isOk = false; + + /** + * if a dayOfMonth is not found in all months, we only need to fix the last + * wrong month to prevent infinite loop + */ + let lastWrongMonth: MonthRange | null = null; + for (const m of months) { + const con = MONTH_CONSTRAINTS[m]; + + for (const day of daysOfMonth) { + if (day <= con) { + isOk = true; + } + } + + if (!isOk) { + // save the month in order to be fixed if all months fails (infinite loop) + lastWrongMonth = m; + console.warn(`Month '${m}' is limited to '${con}' days.`); + } + } + + // infinite loop detected (dayOfMonth is not found in all months) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!isOk && lastWrongMonth !== null) { + const notOkCon = MONTH_CONSTRAINTS[lastWrongMonth]; + for (const notOkDay of daysOfMonth) { + if (notOkDay > notOkCon) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.dayOfMonth[notOkDay]; + const fixedDay = (notOkDay % notOkCon) as DayOfMonthRange; + this.dayOfMonth[fixedDay] = true; + } + } + } + } + + /** + * Calculate the "next" scheduled time + */ + sendAt(): DateTime; + sendAt(i: number): DateTime[]; + sendAt(i?: number): DateTime | DateTime[] { + let date = + this.realDate && this.source instanceof DateTime + ? this.source + : DateTime.local(); + if (this.zone) { + date = date.setZone(this.zone); + } + + if (this.utcOffset != null) { + const offsetHours = parseInt( + // @ts-expect-error old undocumented behavior going to be removed in V3 + this.utcOffset >= 60 || this.utcOffset <= -60 + ? // @ts-expect-error old undocumented behavior going to be removed in V3 + this.utcOffset / 60 + : this.utcOffset + ); + + const offsetMins = + // @ts-expect-error old undocumented behavior going to be removed in V3 + this.utcOffset >= 60 || this.utcOffset <= -60 + ? // @ts-expect-error old undocumented behavior going to be removed in V3 + Math.abs(this.utcOffset - offsetHours * 60) + : 0; + const offsetMinsStr = offsetMins >= 10 ? offsetMins : `0${offsetMins}`; + + let utcZone = 'UTC'; + + // @ts-expect-error old undocumented behavior going to be removed in V3 + if (parseInt(this.utcOffset) < 0) { + utcZone += `${offsetHours === 0 ? '-0' : offsetHours}:${offsetMinsStr}`; + } else { + utcZone += `+${offsetHours}:${offsetMinsStr}`; + } + + date = date.setZone(utcZone); + + if (!date.isValid) { + throw new Error('ERROR: You specified an invalid UTC offset.'); + } + } + + if (this.realDate) { + if (DateTime.local() > date) { + throw new Error('WARNING: Date in past. Will never be fired.'); + } + + return date; + } + + if (i === undefined || isNaN(i) || i < 0) { + // just get the next scheduled time + return this.getNextDateFrom(date); + } else { + // return the next schedule times + const dates: DateTime[] = []; + for (; i > 0; i--) { + date = this.getNextDateFrom(date); + dates.push(date); + } + + return dates; + } + } + + /** + * Get the number of milliseconds in the future at which to fire our callbacks. + */ + getTimeout() { + return Math.max(-1, this.sendAt().toMillis() - DateTime.local().toMillis()); + } + + /** + * writes out a cron string + */ + toString() { + return this.toJSON().join(' '); + } + + /** + * Json representation of the parsed cron syntax. + */ + toJSON() { + return TIME_UNITS.map(unit => { + return this._wcOrAll(unit); + }); + } + + /** + * Get next date matching the specified cron time. + * + * Algorithm: + * - Start with a start date and a parsed crontime. + * - Loop until 5 seconds have passed, or we found the next date. + * - Within the loop: + * - If it took longer than 5 seconds to select a date, throw an exception. + * - Find the next month to run at. + * - Find the next day of the month to run at. + * - Find the next day of the week to run at. + * - Find the next hour to run at. + * - Find the next minute to run at. + * - Find the next second to run at. + * - Check that the chosen time does not equal the current execution. + * - Return the selected date object. + */ + getNextDateFrom(start: Date | DateTime, zone?: string | Zone) { + if (start instanceof Date) { + start = DateTime.fromJSDate(start); + } + let date = start; + const firstDate = start.toMillis(); + if (zone) { + date = date.setZone(zone); + } + if (!this.realDate) { + if (date.millisecond > 0) { + date = date.set({ millisecond: 0, second: date.second + 1 }); + } + } + + if (!date.isValid) { + throw new Error('ERROR: You specified an invalid date.'); + } + + /** + * maximum match interval is 8 years: + * crontab has '* * 29 2 *' and we are on 1 March 2096: + * next matching time will be 29 February 2104 + * source: https://github.com/cronie-crond/cronie/blob/0d669551680f733a4bdd6bab082a0b3d6d7f089c/src/cronnext.c#L401-L403 + */ + const maxMatch = DateTime.now().plus({ years: 8 }); + + // determine next date + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + while (true) { + const diff = date.toMillis() - start.toMillis(); + + // hard stop if the current date is after the maximum match interval + if (date > maxMatch) { + throw new Error( + `Something went wrong. No execution date was found in the next 8 years. + Please provide the following string if you would like to help debug: + Time Zone: ${ + zone?.toString() ?? '""' + } - Cron String: ${this.source.toString()} - UTC offset: ${ + date.offset + } - current Date: ${DateTime.local().toString()}` + ); + } + + if ( + !(date.month in this.month) && + Object.keys(this.month).length !== 12 + ) { + date = date.plus({ months: 1 }); + date = date.set({ day: 1, hour: 0, minute: 0, second: 0 }); + + if (this._forwardDSTJump(0, 0, date)) { + const [isDone, newDate] = this._findPreviousDSTJump(date); + date = newDate; + if (isDone) break; + } + continue; + } + + if ( + !(date.day in this.dayOfMonth) && + Object.keys(this.dayOfMonth).length !== 31 && + !( + this._getWeekDay(date) in this.dayOfWeek && + Object.keys(this.dayOfWeek).length !== 7 + ) + ) { + date = date.plus({ days: 1 }); + date = date.set({ hour: 0, minute: 0, second: 0 }); + + if (this._forwardDSTJump(0, 0, date)) { + const [isDone, newDate] = this._findPreviousDSTJump(date); + date = newDate; + if (isDone) break; + } + continue; + } + + if ( + !(this._getWeekDay(date) in this.dayOfWeek) && + Object.keys(this.dayOfWeek).length !== 7 && + !( + date.day in this.dayOfMonth && + Object.keys(this.dayOfMonth).length !== 31 + ) + ) { + date = date.plus({ days: 1 }); + date = date.set({ hour: 0, minute: 0, second: 0 }); + if (this._forwardDSTJump(0, 0, date)) { + const [isDone, newDate] = this._findPreviousDSTJump(date); + date = newDate; + if (isDone) break; + } + continue; + } + + if (!(date.hour in this.hour) && Object.keys(this.hour).length !== 24) { + const expectedHour = + date.hour === 23 && diff > 86400000 ? 0 : date.hour + 1; + const expectedMinute = date.minute; // expect no change. + + date = date.set({ hour: expectedHour }); + date = date.set({ minute: 0, second: 0 }); + + // When this is the case, Asking luxon to go forward by 1 hour actually made us go forward by more hours... + // This indicates that somewhere between these two time points, a forward DST adjustment has happened. + // When this happens, the job should be scheduled to execute as though the time has come when the jump is made. + // Therefore, the job should be scheduled on the first tick after the forward jump. + if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { + const [isDone, newDate] = this._findPreviousDSTJump(date); + date = newDate; + if (isDone) break; + } + // backwards jumps do not seem to have any problems (i.e. double activations), + // so they need not be handled in a similar way. + + continue; + } + + if ( + !(date.minute in this.minute) && + Object.keys(this.minute).length !== 60 + ) { + const expectedMinute = + date.minute === 59 && diff > 3600000 ? 0 : date.minute + 1; + const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0); + + date = date.set({ minute: expectedMinute }); + date = date.set({ second: 0 }); + + // Same case as with hours: DST forward jump. + // This must be accounted for if a minute increment pushed us to a jumping point. + if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { + const [isDone, newDate] = this._findPreviousDSTJump(date); + date = newDate; + if (isDone) break; + } + + continue; + } + + if ( + !(date.second in this.second) && + Object.keys(this.second).length !== 60 + ) { + const expectedSecond = + date.second === 59 && diff > 60000 ? 0 : date.second + 1; + const expectedMinute = date.minute + (expectedSecond === 60 ? 1 : 0); + const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0); + + date = date.set({ second: expectedSecond }); + + // Seconds can cause it too, imagine 21:59:59 -> 23:00:00. + if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { + const [isDone, newDate] = this._findPreviousDSTJump(date); + date = newDate; + if (isDone) break; + } + + continue; + } + + if (date.toMillis() === firstDate) { + const expectedSecond = date.second + 1; + const expectedMinute = date.minute + (expectedSecond === 60 ? 1 : 0); + const expectedHour = date.hour + (expectedMinute === 60 ? 1 : 0); + + date = date.set({ second: expectedSecond }); + + // Same as always. + if (this._forwardDSTJump(expectedHour, expectedMinute, date)) { + const [isDone, newDate] = this._findPreviousDSTJump(date); + date = newDate; + if (isDone) break; + } + + continue; + } + + break; + } + + return date; + } + + /** + * Search backwards in time 1 minute at a time, to detect a DST forward jump. + * When the jump is found, the range of the jump is investigated to check for acceptable cron times. + * + * A pair is returned, whose first is a boolean representing if an acceptable time was found inside the jump, + * and whose second is a DateTime representing the first millisecond after the jump. + * + * The input date is expected to be decently close to a DST jump. + * Up to a day in the past is checked before an error is thrown. + * @param date + * @return [boolean, DateTime] + */ + private _findPreviousDSTJump(date: DateTime): [boolean, DateTime] { + /** @type number */ + let expectedMinute, expectedHour, actualMinute, actualHour; + /** @type DateTime */ + let maybeJumpingPoint = date; + + // representing one day of backwards checking. If this is hit, the input must be wrong. + const iterationLimit = 60 * 24; + let iteration = 0; + do { + if (++iteration > iterationLimit) { + throw new Error( + `ERROR: This DST checking related function assumes the input DateTime (${ + date.toISO() ?? date.toMillis() + }) is within 24 hours of a DST jump.` + ); + } + + expectedMinute = maybeJumpingPoint.minute - 1; + expectedHour = maybeJumpingPoint.hour; + + if (expectedMinute < 0) { + expectedMinute += 60; + expectedHour = (expectedHour + 24 - 1) % 24; // Subtract 1 hour, but we must account for the -1 case. + } + + maybeJumpingPoint = maybeJumpingPoint.minus({ minute: 1 }); + + actualMinute = maybeJumpingPoint.minute; + actualHour = maybeJumpingPoint.hour; + } while (expectedMinute === actualMinute && expectedHour === actualHour); + + // Setting the seconds and milliseconds to zero is necessary for two reasons: + // Firstly, the range checking function needs the earliest moment after the jump. + // Secondly, this DateTime may be used for scheduling jobs, if there existed a job in the skipped range. + const afterJumpingPoint = maybeJumpingPoint + .plus({ minute: 1 }) // back to the first minute _after_ the jump + .set({ second: 0, millisecond: 0 }); + + // Get the lower bound of the range to check as well. This only has to be accurate down to minutes. + const beforeJumpingPoint = afterJumpingPoint.minus({ second: 1 }); + + if ( + date.month + 1 in this.month && + date.day in this.dayOfMonth && + this._getWeekDay(date) in this.dayOfWeek + ) { + return [ + this._checkTimeInSkippedRange(beforeJumpingPoint, afterJumpingPoint), + afterJumpingPoint + ]; + } + + // no valid time in the range for sure, units that didn't change from the skip mismatch. + return [false, afterJumpingPoint]; + } + + /** + * Given 2 DateTimes, which represent 1 second before and immediately after a DST forward jump, + * checks if a time in the skipped range would have been a valid CronJob time. + * + * Could technically work with just one of these values, extracting the other by adding or subtracting seconds. + * However, this couples the input DateTime to actually being tied to a DST jump, + * which would make the function harder to test. + * This way the logic just tests a range of minutes and hours, regardless if there are skipped time points underneath. + * + * Assumes the DST jump started no earlier than 0:00 and jumped forward by at least 1 minute, to at most 23:59. + * i.e. The day is assumed constant, but the jump is not assumed to be an hour long. + * Empirically, it is almost always one hour, but very, very rarely 30 minutes. + * + * Assumes dayOfWeek, dayOfMonth and month match all match, so only the hours, minutes and seconds are to be checked. + * @param {DateTime} beforeJumpingPoint + * @param {DateTime} afterJumpingPoint + * @returns {boolean} + */ + private _checkTimeInSkippedRange( + beforeJumpingPoint: DateTime, + afterJumpingPoint: DateTime + ) { + // start by getting the first minute & hour inside the skipped range. + const startingMinute = (beforeJumpingPoint.minute + 1) % 60; + const startingHour = + (beforeJumpingPoint.hour + (startingMinute === 0 ? 1 : 0)) % 24; + + const hourRangeSize = afterJumpingPoint.hour - startingHour + 1; + const isHourJump = startingMinute === 0 && afterJumpingPoint.minute === 0; + + // There exist DST jumps other than 1 hour long, and the function is built to deal with it. + // It may be overkill to assume some cases, but it shouldn't cost much at runtime. + // https://en.wikipedia.org/wiki/Daylight_saving_time_by_country + if (hourRangeSize === 2 && isHourJump) { + // Exact 1 hour jump, most common real-world case. + // There is no need to check minutes and seconds, as any value would suffice. + return startingHour in this.hour; + } else if (hourRangeSize === 1) { + // less than 1 hour jump, rare but does exist. + return ( + startingHour in this.hour && + this._checkTimeInSkippedRangeSingleHour( + startingMinute, + afterJumpingPoint.minute + ) + ); + } else { + // non-round or multi-hour jump. (does not exist in the real world at the time of writing) + return this._checkTimeInSkippedRangeMultiHour( + startingHour, + startingMinute, + afterJumpingPoint.hour, + afterJumpingPoint.minute + ); + } + } + + /** + * Component of checking if a CronJob time existed in a DateTime range skipped by DST. + * This subroutine makes a further assumption that the skipped range is fully contained in one hour, + * and that all other larger units are valid for the job. + * + * for example a jump from 02:00:00 to 02:30:00, but not from 02:00:00 to 03:00:00. + * @see _checkTimeInSkippedRange + * + * This is done by checking if any minute in startMinute - endMinute is valid, excluding endMinute. + * For endMinute, there is only a match if the 0th second is a valid time. + */ + private _checkTimeInSkippedRangeSingleHour( + startMinute: number, + endMinute: number + ) { + for (let minute = startMinute; minute < endMinute; ++minute) { + if (minute in this.minute) return true; + } + + // Unless the very last second of the jump matched, there is no match. + return endMinute in this.minute && 0 in this.second; + } + + /** + * Component of checking if a CronJob time existed in a DateTime range skipped by DST. + * This subroutine assumes the jump touches at least 2 hours, but the jump does not necessarily fully contain these hours. + * + * @see _checkTimeInSkippedRange + * + * This is done by defining the minutes to check for the first and last hour, + * and checking all 60 minutes for any hours in between them. + * + * If any hour x minute combination is a valid time, true is returned. + * The endMinute x endHour combination is only checked with the 0th second, since the rest would be out of the range. + * + * @param startHour {number} + * @param startMinute {number} + * @param endHour {number} + * @param endMinute {number} + */ + private _checkTimeInSkippedRangeMultiHour( + startHour: number, + startMinute: number, + endHour: number, + endMinute: number + ) { + if (startHour >= endHour) { + throw new Error( + `ERROR: This DST checking related function assumes the forward jump starting hour (${startHour}) is less than the end hour (${endHour})` + ); + } + + /** @type number[] */ + const firstHourMinuteRange = Array.from( + { length: 60 - startMinute }, + (_, k) => startMinute + k + ); + /** @type {number[]} The final minute is not contained on purpose. Every minute in this range represents one for which any second is valid. */ + const lastHourMinuteRange = Array.from({ length: endMinute }, (_, k) => k); + /** @type number[] */ + const middleHourMinuteRange = Array.from({ length: 60 }, (_, k) => k); + + /** @type (number) => number[] */ + const selectRange = (forHour: number) => { + if (forHour === startHour) { + return firstHourMinuteRange; + } else if (forHour === endHour) { + return lastHourMinuteRange; + } else { + return middleHourMinuteRange; + } + }; + + // Include the endHour: Selecting the right range still ensures no values outside the skip are checked. + for (let hour = startHour; hour <= endHour; ++hour) { + if (!(hour in this.hour)) continue; + + // The hour matches, so if the minute is in the range, we have a match! + const usingRange = selectRange(hour); + + for (const minute of usingRange) { + // All minutes in any of the selected ranges represent minutes which are fully contained in the jump, + // So we need not check the seconds. If the minute is in there, it is a match. + if (minute in this.minute) return true; + } + } + + // The endMinute of the endHour was not checked in the loop, because only the 0th second of it is in the range. + // Arriving here means no match was found yet, but this final check may turn up as a match. + return endHour in this.hour && endMinute in this.minute && 0 in this.second; + } + + /** + * Given expected and actual hours and minutes, report if a DST forward jump occurred. + * + * This is the case when the expected is smaller than the acutal. + * + * It is not sufficient to check only hours, because some parts of the world apply DST by shifting in minutes. + * Better to account for it by checking minutes too, before an Australian of Lord Howe Island call us. + * @param expectedHour + * @param expectedMinute + * @param {DateTime} actualDate + */ + private _forwardDSTJump( + expectedHour: number, + expectedMinute: number, + actualDate: DateTime + ) { + const actualHour = actualDate.hour; + const actualMinute = actualDate.minute; + + const didHoursJumped = expectedHour % 24 < actualHour; + const didMinutesJumped = expectedMinute % 60 < actualMinute; + + return didHoursJumped || didMinutesJumped; + } + + /** + * wildcard, or all params in array (for to string) + */ + private _wcOrAll(unit: TimeUnit) { + if (this._hasAll(unit)) { + return '*'; + } + + const all = []; + for (const time in this[unit]) { + all.push(time); + } + + return all.join(','); + } + + private _hasAll(unit: TimeUnit) { + const constraints = CONSTRAINTS[unit]; + const low = constraints[0]; + const high = + unit === TIME_UNITS_MAP.DAY_OF_WEEK ? constraints[1] - 1 : constraints[1]; + + for (let i = low, n = high; i < n; i++) { + if (!(i in this[unit])) { + return false; + } + } + + return true; + } + + /* + * Parse the cron syntax into something useful for selecting the next execution time. + * + * Algorithm: + * - Replace preset + * - Replace aliases in the source. + * - Trim string and split for processing. + * - Loop over split options (ms -> month): + * - Get the value (or default) in the current position. + * - Parse the value. + */ + private _parse(source: string) { + source = source.toLowerCase(); + + if (Object.keys(PRESETS).includes(source)) { + source = PRESETS[source as keyof typeof PRESETS]; + } + + source = source.replace(/[a-z]{1,3}/gi, (alias: string) => { + if (Object.keys(ALIASES).includes(alias)) { + return ALIASES[alias as keyof typeof ALIASES].toString(); + } + + throw new Error(`Unknown alias: ${alias}`); + }); + + const units = source.trim().split(/\s+/); + + // seconds are optional + if (units.length < TIME_UNITS_LEN - 1) { + throw new Error('Too few fields'); + } + + if (units.length > TIME_UNITS_LEN) { + throw new Error('Too many fields'); + } + + const unitsLen = units.length; + for (const unit of TIME_UNITS) { + const i = TIME_UNITS.indexOf(unit); + // If the split source string doesn't contain all digits, + // assume defaults for first n missing digits. + // This adds support for 5-digit standard cron syntax + const cur = + units[i - (TIME_UNITS_LEN - unitsLen)] ?? PARSE_DEFAULTS[unit]; + this._parseField(cur, unit); + } + } + + /* + * Parse individual field from the cron syntax provided. + * + * Algorithm: + * - Split field by commas aand check for wildcards to ensure proper user. + * - Replace wildcard values with - boundaries. + * - Split field by commas and then iterate over ranges inside field. + * - If range matches pattern then map over matches using replace (to parse the range by the regex pattern) + * - Starting with the lower bounds of the range iterate by step up to the upper bounds and toggle the CronTime field value flag on. + */ + + private _parseField(value: string, unit: TimeUnit) { + const typeObj = this[unit] as TimeUnitField; + let pointer: Ranges[typeof unit]; + + const constraints = CONSTRAINTS[unit]; + const low = constraints[0]; + const high = constraints[1]; + + const fields = value.split(','); + fields.forEach(field => { + const wildcardIndex = field.indexOf('*'); + if (wildcardIndex !== -1 && wildcardIndex !== 0) { + throw new Error(`Field (${field}) has an invalid wildcard expression`); + } + }); + + // * is a shortcut to [low-high] range for the field + value = value.replace(RE_WILDCARDS, `${low}-${high}`); + + // commas separate information, so split based on those + const allRanges = value.split(','); + + for (const range of allRanges) { + const match = [...range.matchAll(RE_RANGE)][0]; + if (match?.[1] !== undefined) { + const [, mLower, mUpper, mStep] = match; + let lower = parseInt(mLower, 10); + let upper = mUpper !== undefined ? parseInt(mUpper, 10) : undefined; + + const wasStepDefined = mStep !== undefined; + if (mStep === '0') { + throw new Error(`Field (${unit}) has a step of zero`); + } + const step = parseInt(mStep ?? '1', 10); + + if (upper !== undefined && lower > upper) { + throw new Error(`Field (${unit}) has an invalid range`); + } + + const isOutOfRange = + lower < low || + (upper !== undefined && upper > high) || + (upper === undefined && lower > high); + + if (isOutOfRange) { + throw new Error(`Field value (${value}) is out of range`); + } + + // Positive integer higher than constraints[0] + lower = Math.min(Math.max(low, ~~Math.abs(lower)), high); + + // Positive integer lower than constraints[1] + if (upper !== undefined) { + upper = Math.min(high, ~~Math.abs(upper)); + } else { + // If step is provided, the default upper range is the highest value + upper = wasStepDefined ? high : lower; + } + + // Count from the lower barrier to the upper + // forcing type cast here since we checked above that + // we are between constraint bounds + pointer = lower as typeof pointer; + + do { + typeObj[pointer] = true; // mutates the field objects values inside CronTime + pointer += step; + } while (pointer <= upper); + + // merge day 7 into day 0 (both Sunday), and remove day 7 + // since we work with day-of-week 0-6 under the hood + if (unit === 'dayOfWeek') { + if (!typeObj[0] && !!typeObj[7]) typeObj[0] = typeObj[7]; + delete typeObj[7]; + } + } else { + throw new Error(`Field (${unit}) cannot be parsed`); + } + } + } +} diff --git a/src/types/cron.types.ts b/src/types/cron.types.ts new file mode 100644 index 00000000..99b465fa --- /dev/null +++ b/src/types/cron.types.ts @@ -0,0 +1,72 @@ +import { SpawnOptions } from 'child_process'; +import { DateTime } from 'luxon'; +import { CONSTRAINTS, TIME_UNITS_MAP } from '../constants'; +import { CronJob } from '../job'; +import { IntRange } from './utils'; + +export interface CronJobParams { + cronTime: string | Date | DateTime; + onTick: CronCommand; + onComplete?: CronCommand | null; + start?: boolean | null; + timeZone?: string | null; + context?: unknown | null; + runOnInit?: boolean | null; + utcOffset?: string | number | null; + unrefTimeout?: boolean | null; +} + +export type CronCommand = + /** + * TODO: find out how to type the context correctly, based on + * if the "context" was provided to the CronJob constructor + * leaving "any" for now... + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + | ((this: CronJob | any) => void) + | string + | { + command: string; + args?: readonly string[] | null; + options?: SpawnOptions | null; + }; + +export type TimeUnit = (typeof TIME_UNITS_MAP)[keyof typeof TIME_UNITS_MAP]; + +export type TimeUnitField = Partial< + Record +>; + +export interface Ranges { + second: SecondRange; + minute: MinuteRange; + hour: HourRange; + dayOfMonth: DayOfMonthRange; + month: MonthRange; + dayOfWeek: DayOfWeekRange; +} + +export type SecondRange = IntRange< + (typeof CONSTRAINTS)['second'][0], + (typeof CONSTRAINTS)['second'][1] +>; +export type MinuteRange = IntRange< + (typeof CONSTRAINTS)['minute'][0], + (typeof CONSTRAINTS)['minute'][1] +>; +export type HourRange = IntRange< + (typeof CONSTRAINTS)['hour'][0], + (typeof CONSTRAINTS)['hour'][1] +>; +export type DayOfMonthRange = IntRange< + (typeof CONSTRAINTS)['dayOfMonth'][0], + (typeof CONSTRAINTS)['dayOfMonth'][1] +>; +export type MonthRange = IntRange< + (typeof CONSTRAINTS)['month'][0], + (typeof CONSTRAINTS)['month'][1] +>; +export type DayOfWeekRange = IntRange< + (typeof CONSTRAINTS)['dayOfWeek'][0], + (typeof CONSTRAINTS)['dayOfWeek'][1] +>; diff --git a/src/types/utils.ts b/src/types/utils.ts new file mode 100644 index 00000000..57d3718e --- /dev/null +++ b/src/types/utils.ts @@ -0,0 +1,14 @@ +export type Enumerate< + N extends number, + WithTail extends boolean = true, + Acc extends number[] = [] +> = Acc['length'] extends N + ? WithTail extends true + ? [...Acc, Acc['length']][number] + : Acc[number] + : Enumerate; + +export type IntRange = Exclude< + Enumerate, + Enumerate +>; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..6f53350b --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,7 @@ +import { Ranges } from './types/cron.types'; + +export const getRecordKeys = ( + record: Partial> +) => { + return Object.keys(record) as unknown as (keyof typeof record)[]; +}; diff --git a/tests/cron.test.js b/tests/cron.test.ts similarity index 85% rename from tests/cron.test.js rename to tests/cron.test.ts index bcf7f924..f996592a 100644 --- a/tests/cron.test.js +++ b/tests/cron.test.ts @@ -1,13 +1,13 @@ -/* eslint-disable no-new */ -const sinon = require('sinon'); -const cron = require('../lib/cron'); +import { DateTime } from 'luxon'; +import sinon from 'sinon'; +import { CronJob, CronTime } from '../src'; describe('cron', () => { describe('with seconds', () => { it('should run every second (* * * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('* * * * * *', callback, null, true); + const job = new CronJob('* * * * * *', callback, null, true); expect(callback).not.toHaveBeenCalled(); clock.tick(1000); @@ -20,7 +20,7 @@ describe('cron', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( '* * * * * *', callback, () => { @@ -38,7 +38,7 @@ describe('cron', () => { it('should use standard cron no-seconds syntax (* * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('* * * * *', callback, null, true); + const job = new CronJob('* * * * *', callback, null, true); clock.tick(1000); // tick second @@ -53,7 +53,7 @@ describe('cron', () => { it('should run every second for 5 seconds (* * * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('* * * * * *', callback, null, true); + const job = new CronJob('* * * * * *', callback, null, true); for (let i = 0; i < 5; i++) clock.tick(1000); job.stop(); clock.restore(); @@ -63,7 +63,7 @@ describe('cron', () => { it('should run every second for 5 seconds with oncomplete (* * * * * *)', done => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob( + const job = new CronJob( '* * * * * *', callback, () => { @@ -80,7 +80,7 @@ describe('cron', () => { it('should run every second for 5 seconds (*/1 * * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('*/1 * * * * *', callback, null, true); + const job = new CronJob('*/1 * * * * *', callback, null, true); for (let i = 0; i < 5; i++) clock.tick(1000); job.stop(); clock.restore(); @@ -90,7 +90,7 @@ describe('cron', () => { it('should run every 2 seconds for 1 seconds (*/2 * * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('*/2 * * * * *', callback, null, true); + const job = new CronJob('*/2 * * * * *', callback, null, true); clock.tick(1000); job.stop(); clock.restore(); @@ -100,7 +100,7 @@ describe('cron', () => { it('should run every 2 seconds for 5 seconds (*/2 * * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('*/2 * * * * *', callback, null, true); + const job = new CronJob('*/2 * * * * *', callback, null, true); for (let i = 0; i < 5; i++) clock.tick(1000); job.stop(); clock.restore(); @@ -110,7 +110,7 @@ describe('cron', () => { it('should run every second for 5 seconds with oncomplete (*/1 * * * * *)', done => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( '*/1 * * * * *', callback, () => { @@ -127,7 +127,7 @@ describe('cron', () => { it('should run every second for a range ([start]-[end] * * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('0-8 * * * * *', callback, null, true); + const job = new CronJob('0-8 * * * * *', callback, null, true); clock.tick(10000); job.stop(); clock.restore(); @@ -137,7 +137,7 @@ describe('cron', () => { it('should run every second for a range ([start]-[end] * * * * *) with onComplete', done => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( '0-8 * * * * *', callback, () => { @@ -154,7 +154,7 @@ describe('cron', () => { it('should default to full range when upper range not provided (1/2 * * * * *)', done => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( '1/2 * * * * *', callback, () => { @@ -171,7 +171,7 @@ describe('cron', () => { it('should run every second (* * * * * *) using the object constructor', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '* * * * * *', onTick: callback, start: true @@ -185,7 +185,7 @@ describe('cron', () => { it('should run every second with oncomplete (* * * * * *) using the object constructor', done => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '* * * * * *', onTick: callback, onComplete: () => { @@ -204,8 +204,8 @@ describe('cron', () => { it('should fire every 60 min', () => { const clock = sinon.useFakeTimers(); const m60 = 60 * 60 * 1000; - const l = []; - const job = new cron.CronJob( + const l: number[] = []; + const job = new CronJob( '00 30 * * * *', () => { l.push(Math.floor(Date.now() / 60000)); @@ -226,7 +226,7 @@ describe('cron', () => { it('should run every 45 minutes for 2 hours (0 */45 * * * *)', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob('0 */45 * * * *', callback, null, true); + const job = new CronJob('0 */45 * * * *', callback, null, true); for (let i = 0; i < 2; i++) clock.tick(60 * 60 * 1000); job.stop(); clock.restore(); @@ -236,7 +236,7 @@ describe('cron', () => { it('should run every 45 minutes for 2 hours (0 */45 * * * *) with onComplete', done => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( '0 */45 * * * *', callback, () => { @@ -254,7 +254,7 @@ describe('cron', () => { it('should start and stop job from outside', done => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( '* * * * * *', function () { callback(); @@ -273,11 +273,11 @@ describe('cron', () => { it('should start and stop job from inside (default context)', done => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - new cron.CronJob( + new CronJob( '* * * * * *', function () { callback(); - this.stop(); + (this as CronJob).stop(); }, () => { expect(callback).toHaveBeenCalledTimes(1); @@ -296,7 +296,7 @@ describe('cron', () => { const s = d.getSeconds() + 1; d.setSeconds(s); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( d, () => { const t = new Date(); @@ -318,7 +318,7 @@ describe('cron', () => { const s = d.getSeconds() + 1; d.setSeconds(s); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( d, () => { const t = new Date(); @@ -342,7 +342,7 @@ describe('cron', () => { const d = new Date().getTime() + 31 * 86400 * 1000; - const job = cron.job(new Date(d), callback); + const job = new CronJob(new Date(d), callback); job.start(); clock.tick(1000); @@ -358,7 +358,7 @@ describe('cron', () => { const d = new Date().getTime() + 31 * 86400 * 1000; - const job = cron.job({ + const job = CronJob.from({ cronTime: new Date(d), onTick: callback, runOnInit: true @@ -379,7 +379,7 @@ describe('cron', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = cron.job({ + const job = CronJob.from({ cronTime: '* * * * * *', onTick: callback, runOnInit: true @@ -401,12 +401,11 @@ describe('cron', () => { it('should run a job using cron syntax with a timezone', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const luxon = require('luxon'); let zone = 'America/Chicago'; // New Orleans time - let t = luxon.DateTime.local().setZone(zone); + let t = DateTime.local().setZone(zone); // Current time - const d = luxon.DateTime.local(); + const d = DateTime.local(); // If current time is New Orleans time, switch to Los Angeles.. if (t.hour === d.hour) { @@ -422,8 +421,8 @@ describe('cron', () => { // Run a job designed to be executed at a given // time in `zone`, making sure that it is a different // hour than local time. - const job = new cron.CronJob( - t.second + ' ' + t.minute + ' ' + t.hour + ' * * *', + const job = new CronJob( + `${t.second} ${t.minute} ${t.hour} * * *`, callback, null, true, @@ -439,14 +438,13 @@ describe('cron', () => { it('should run a job using cron syntax with a "UTC+HH:mm" offset as timezone', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const luxon = require('luxon'); // Current time - const d = luxon.DateTime.local(); + const d = DateTime.local(); // Current time with zone offset let zone = 'UTC+5:30'; - let t = luxon.DateTime.local().setZone(zone); + let t = DateTime.local().setZone(zone); // If current offset is UTC+5:30, switch to UTC+6:30.. if (t.hour === d.hour && t.minute === d.minute) { @@ -462,8 +460,8 @@ describe('cron', () => { // Run a job designed to be executed at a given // time in `zone`, making sure that it is a different // hour than local time. - const job = new cron.CronJob( - t.second + ' ' + t.minute + ' ' + t.hour + ' * * *', + const job = new CronJob( + `${t.second} ${t.minute} ${t.hour} * * *`, callback, null, true, @@ -477,12 +475,11 @@ describe('cron', () => { }); it('should run a job using a date', () => { - const luxon = require('luxon'); let zone = 'America/Chicago'; // New Orleans time - let t = luxon.DateTime.local().setZone(zone); + let t = DateTime.local().setZone(zone); // Current time - let d = luxon.DateTime.local(); + let d = DateTime.local(); // If current time is New Orleans time, switch to Los Angeles.. if (t.hour === d.hour) { @@ -494,7 +491,7 @@ describe('cron', () => { d = d.plus({ seconds: 1 }); const clock = sinon.useFakeTimers(d.valueOf()); const callback = jest.fn(); - const job = new cron.CronJob(d.toJSDate(), callback, null, true, zone); + const job = new CronJob(d.toJSDate(), callback, null, true, zone); clock.tick(1000); clock.restore(); job.stop(); @@ -504,7 +501,7 @@ describe('cron', () => { it('should test if timezone is valid.', () => { expect(() => { // eslint-disable-next-line no-new - new cron.CronJob({ + CronJob.from({ cronTime: '* * * * * *', onTick: () => {}, timeZone: 'fake/timezone' @@ -516,10 +513,10 @@ describe('cron', () => { it('should scope onTick to running job', () => { const clock = sinon.useFakeTimers(); - const job = new cron.CronJob( + const job = new CronJob( '* * * * * *', function () { - expect(job).toBeInstanceOf(cron.CronJob); + expect(job).toBeInstanceOf(CronJob); expect(job).toEqual(this); }, null, @@ -535,10 +532,10 @@ describe('cron', () => { it('should scope onTick to object', () => { const clock = sinon.useFakeTimers(); - const job = new cron.CronJob( + const job = new CronJob( '* * * * * *', function () { - expect(this.hello).toEqual('world'); + expect((this as { hello: string }).hello).toBe('world'); expect(job).not.toEqual(this); }, null, @@ -556,10 +553,10 @@ describe('cron', () => { it('should scope onTick to object within constructor object', () => { const clock = sinon.useFakeTimers(); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '* * * * * *', onTick: function () { - expect(this.hello).toEqual('world'); + expect((this as { hello: string }).hello).toBe('world'); expect(job).not.toEqual(this); }, start: true, @@ -574,10 +571,10 @@ describe('cron', () => { it('should not get into an infinite loop on invalid times', () => { expect(() => { - new cron.CronJob( + new CronJob( '* 60 * * * *', () => { - expect.ok(true); + expect(true).toBe(true); }, null, true @@ -585,10 +582,10 @@ describe('cron', () => { }).toThrow(); expect(() => { - new cron.CronJob( + new CronJob( '* * 24 * * *', () => { - expect.ok(true); + expect(true).toBe(true); }, null, true @@ -604,7 +601,7 @@ describe('cron', () => { d.setHours(23); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob('0 0 0 1 * *', callback, null, true); + const job = new CronJob('0 0 0 1 * *', callback, null, true); clock.tick(1001); expect(callback).toHaveBeenCalledTimes(1); @@ -625,7 +622,7 @@ describe('cron', () => { toFake: ['setTimeout'] }); - const job = new cron.CronJob('0 * * * * *', callback, null, true); + const job = new CronJob('0 * * * * *', callback, null, true); clock.tick(60000); expect(callback).toHaveBeenCalledTimes(0); @@ -642,7 +639,7 @@ describe('cron', () => { d.setHours(23); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '59 59 3 * * *', onTick: callback, start: true, @@ -665,7 +662,7 @@ describe('cron', () => { d.setHours(0); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '0 2-6/2 * * * *', onTick: callback, start: true @@ -692,7 +689,7 @@ describe('cron', () => { d.setHours(0); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '00 * * * * *', onTick: callback, start: true @@ -716,7 +713,7 @@ describe('cron', () => { d.setHours(0); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '00 30 00 * * *', onTick: callback, start: true @@ -744,7 +741,7 @@ describe('cron', () => { d.setHours(23); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '00 * * * * *', onTick: callback, start: true, @@ -767,7 +764,7 @@ describe('cron', () => { d.setHours(0); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '00 30 00 * * *', onTick: callback, start: true, @@ -794,7 +791,7 @@ describe('cron', () => { const d = new Date(2015, 1, 1, 1, 1, 41, 0); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: '* * * * *', onTick: callback, start: true @@ -819,7 +816,7 @@ describe('cron', () => { const d = new Date(2096, 2, 1); const clock = sinon.useFakeTimers(d.getTime()); - const job = new cron.CronJob({ + const job = CronJob.from({ cronTime: ' * * 29 2 *', onTick: callback, start: true @@ -841,14 +838,13 @@ describe('cron', () => { it('should run a job using cron syntax with number format utcOffset', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const luxon = require('luxon'); // Current time - const t = luxon.DateTime.local(); + const t = DateTime.local(); // UTC Offset decreased by an hour const utcOffset = t.offset - 60; - const job = new cron.CronJob( - t.second + ' ' + t.minute + ' ' + t.hour + ' * * *', + const job = new CronJob( + `${t.second} ${t.minute} ${t.hour} * * *`, callback, null, true, @@ -871,9 +867,8 @@ describe('cron', () => { it('should run a job using cron syntax with numeric format utcOffset with minute support', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const luxon = require('luxon'); // Current time - const t = luxon.DateTime.local(); + const t = DateTime.local(); /** * in order to avoid the minute offset being treated as hours (when `-60 < utcOffset < 60`) regardless of the local timezone, @@ -886,8 +881,8 @@ describe('cron', () => { // UTC Offset decreased by minutesOffset const utcOffset = t.offset - minutesOffset; - const job = new cron.CronJob( - t.second + ' ' + t.minute + ' ' + t.hour + ' * * *', + const job = new CronJob( + `${t.second} ${t.minute} ${t.hour} * * *`, callback, null, true, @@ -916,19 +911,18 @@ describe('cron', () => { it('should run a job using cron syntax with string format utcOffset', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const luxon = require('luxon'); // Current time - const t = luxon.DateTime.local(); + const t = DateTime.local(); // UTC Offset decreased by an hour (string format '(+/-)HH:mm') // We support only HH support in offset as we support string offset in Timezone. const minutesOffset = t.offset - Math.floor((t.offset - 60) / 60) * 60; const utcOffset = t.offset - minutesOffset; - const utcOffsetString = `${utcOffset > 0 ? '+' : '-'}${( - '0' + Math.floor(Math.abs(utcOffset) / 60) - ).slice(-2)}:${('0' + (utcOffset % 60)).slice(-2)}`; + const utcOffsetString = `${utcOffset > 0 ? '+' : '-'}${`0${Math.floor( + Math.abs(utcOffset) / 60 + )}`.slice(-2)}:${`0${utcOffset % 60}`.slice(-2)}`; - const job = new cron.CronJob( - t.second + ' ' + t.minute + ' ' + t.hour + ' * * *', + const job = new CronJob( + `${t.second} ${t.minute} ${t.hour} * * *`, callback, null, true, @@ -953,7 +947,7 @@ describe('cron', () => { const clock = sinon.useFakeTimers(); const callback = jest.fn(); - const job = new cron.CronJob( + const job = new CronJob( '* * * * * *', callback, null, @@ -975,7 +969,7 @@ describe('cron', () => { it('should be able to detect out of range days of month', () => { expect(() => { - new cron.CronTime('* * 32 FEB *'); + new CronTime('* * 32 FEB *'); }).toThrow(); }); }); @@ -985,12 +979,12 @@ describe('cron', () => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); job.start(); clock.tick(1000); - const time = cron.time('*/2 * * * * *'); + const time = new CronTime('*/2 * * * * *'); job.setTime(time); clock.tick(4000); @@ -1004,13 +998,13 @@ describe('cron', () => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); job.start(); clock.tick(1000); job.stop(); - const time = cron.time('*/2 * * * * *'); + const time = new CronTime('*/2 * * * * *'); job.setTime(time); clock.tick(4000); @@ -1022,8 +1016,9 @@ describe('cron', () => { it('should setTime with invalid object', () => { const callback = jest.fn(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); expect(() => { + // @ts-expect-error time parameter cannot be undefined job.setTime(undefined); }).toThrow(); }); @@ -1032,7 +1027,7 @@ describe('cron', () => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); const time = new Date(); job.start(); @@ -1040,6 +1035,7 @@ describe('cron', () => { clock.tick(1000); expect(() => { + // @ts-expect-error time parameter but be an instance of CronTime job.setTime(time); }).toThrow(); @@ -1053,7 +1049,7 @@ describe('cron', () => { it('should give the next date to run at', () => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); const d = Date.now(); expect(job.nextDate().toMillis()).toEqual(d + 1000); @@ -1064,7 +1060,7 @@ describe('cron', () => { it('should give the next 5 dates to run at', () => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); const d = Date.now(); expect(job.nextDates(5).map(d => d.toMillis())).toEqual([ @@ -1081,7 +1077,7 @@ describe('cron', () => { it('should give an empty array when called without argument', () => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); expect(job.nextDates()).toHaveLength(0); @@ -1094,7 +1090,7 @@ describe('cron', () => { const clock = sinon.useFakeTimers(); const d = new Date(); d.setMilliseconds(2147485647 * 2); // MAXDELAY in `job.js` + 2000. - const job = new cron.CronJob(d, callback); + const job = new CronJob(d, callback); job.start(); clock.tick(2147483648); expect(callback).toHaveBeenCalledTimes(0); @@ -1107,11 +1103,11 @@ describe('cron', () => { it('should give the last execution date', () => { const callback = jest.fn(); const clock = sinon.useFakeTimers(); - const job = new cron.CronJob('* * * * * *', callback); + const job = new CronJob('* * * * * *', callback); job.start(); clock.tick(1000); expect(callback).toHaveBeenCalledTimes(1); - expect(job.lastDate().getTime()).toEqual(1000); + expect(job.lastDate()?.getTime()).toBe(1000); job.stop(); clock.restore(); }); diff --git a/tests/crontime.test.js b/tests/crontime.test.ts similarity index 51% rename from tests/crontime.test.js rename to tests/crontime.test.ts index dee4a45d..5dd65fda 100644 --- a/tests/crontime.test.js +++ b/tests/crontime.test.ts @@ -1,396 +1,405 @@ -/* eslint-disable no-new */ -const sinon = require('sinon'); -const luxon = require('luxon'); -const cron = require('../lib/cron'); +import { DateTime } from 'luxon'; +import sinon from 'sinon'; +import { CronTime } from '../src'; describe('crontime', () => { it('should test stars (* * * * * *)', () => { expect(() => { - new cron.CronTime('* * * * * *'); + new CronTime('* * * * * *'); }).not.toThrow(); }); it('should test digit (0 * * * * *)', () => { expect(() => { - new cron.CronTime('0 * * * * *'); + new CronTime('0 * * * * *'); }).not.toThrow(); }); it('should test multi digits (08 * * * * *)', () => { expect(() => { - new cron.CronTime('08 * * * * *'); + new CronTime('08 * * * * *'); }).not.toThrow(); }); it('should test all digits (08 8 8 8 8 5)', () => { expect(() => { - new cron.CronTime('08 * * * * *'); + new CronTime('08 * * * * *'); }).not.toThrow(); }); it('should test too many digits (08 8 8 8 8 5)', () => { expect(() => { - new cron.CronTime('08 * * * * *'); + new CronTime('08 * * * * *'); }).not.toThrow(); }); it('should test standard cron format (* * * * *)', () => { expect(() => { - new cron.CronTime('* * * * *'); + new CronTime('* * * * *'); }).not.toThrow(); }); it('should test standard cron format (8 8 8 8 5)', () => { - const standard = new cron.CronTime('8 8 8 8 5'); - const extended = new cron.CronTime('0 8 8 8 8 5'); + const standard = new CronTime('8 8 8 8 5'); + const extended = new CronTime('0 8 8 8 8 5'); - expect(standard.dayOfWeek).toEqual(extended.dayOfWeek); - expect(standard.month).toEqual(extended.month); - expect(standard.dayOfMonth).toEqual(extended.dayOfMonth); - expect(standard.hour).toEqual(extended.hour); - expect(standard.minute).toEqual(extended.minute); - expect(standard.second).toEqual(extended.second); + // @ts-expect-error deleting for comparaison purposes + delete standard.source; + // @ts-expect-error deleting for comparaison purposes + delete extended.source; + + expect(extended).toEqual(standard); }); it('should test hyphen (0-10 * * * * *)', () => { expect(() => { - new cron.CronTime('0-10 * * * * *'); + new CronTime('0-10 * * * * *'); }).not.toThrow(); }); it('should test multi hyphens (0-10 0-10 * * * *)', () => { expect(() => { - new cron.CronTime('0-10 0-10 * * * *'); + new CronTime('0-10 0-10 * * * *'); }).not.toThrow(); }); it('should test all hyphens (0-10 0-10 1-10 1-10 1-7 0-1)', () => { expect(() => { - new cron.CronTime('0-10 0-10 1-10 1-10 1-7 0-1'); + new CronTime('0-10 0-10 1-10 1-10 1-7 0-1'); }).not.toThrow(); }); it('should accept all valid ranges (0-59 0-59 0-23 1-31 1-12 0-7)', () => { expect(() => { - new cron.CronTime('0-59 0-59 0-23 1-31 1-12 0-7'); + new CronTime('0-59 0-59 0-23 1-31 1-12 0-7'); }).not.toThrow(); }); it('should test comma (0,10 * * * * *)', () => { expect(() => { - new cron.CronTime('0,10 * * * * *'); + new CronTime('0,10 * * * * *'); }).not.toThrow(); }); it('should test multi commas (0,10 0,10 * * * *)', () => { expect(() => { - new cron.CronTime('0,10 0,10 * * * *'); + new CronTime('0,10 0,10 * * * *'); }).not.toThrow(); }); it('should test all commas (0,10 0,10 1,10 1,10 1,7 0,1)', () => { expect(() => { - new cron.CronTime('0,10 0,10 1,10 1,10 1,7 0,1'); + new CronTime('0,10 0,10 1,10 1,10 1,7 0,1'); }).not.toThrow(); }); it('should test alias (* * * * jan *)', () => { expect(() => { - new cron.CronTime('* * * * jan *'); + new CronTime('* * * * jan *'); }).not.toThrow(); }); it('should test multi aliases (* * * * jan,feb *)', () => { expect(() => { - new cron.CronTime('* * * * jan,feb *'); + new CronTime('* * * * jan,feb *'); }).not.toThrow(); }); it('should test all aliases (* * * * jan,feb mon,tue)', () => { expect(() => { - new cron.CronTime('* * * * jan,feb mon,tue'); + new CronTime('* * * * jan,feb mon,tue'); }).not.toThrow(); }); it('should test unknown alias (* * * * jar *)', () => { expect(() => { - new cron.CronTime('* * * * jar *'); + new CronTime('* * * * jar *'); }).toThrow(); }); it('should test unknown alias - short (* * * * j *)', () => { expect(() => { - new cron.CronTime('* * * * j *'); + new CronTime('* * * * j *'); }).toThrow(); }); it('should be case-insensitive for aliases (* * * * JAN,FEB MON,TUE)', () => { expect(() => { - new cron.CronTime('* * * * JAN,FEB MON,TUE', null, null); + new CronTime('* * * * JAN,FEB MON,TUE', null, null); }).not.toThrow(); }); it('should test too few fields', () => { expect(() => { - new cron.CronTime('* * * *', null, null); + new CronTime('* * * *', null, null); }).toThrow(); }); it('should test too many fields', () => { expect(() => { - new cron.CronTime('* * * * * * *', null, null); + new CronTime('* * * * * * *', null, null); }).toThrow(); }); it('should return the same object with 0 & 7 as Sunday (except "source" prop)', () => { - const sunday0 = new cron.CronTime('* * * * 0', null, null); - const sunday7 = new cron.CronTime('* * * * 7', null, null); + const sunday0 = new CronTime('* * * * 0', null, null); + const sunday7 = new CronTime('* * * * 7', null, null); + + // @ts-expect-error deleting for comparaison purposes delete sunday0.source; + // @ts-expect-error deleting for comparaison purposes delete sunday7.source; + expect(sunday7).toEqual(sunday0); }); describe('should test out of range values', () => { it('should test out of range minute', () => { expect(() => { - new cron.CronTime('-1 * * * *', null, null); + new CronTime('-1 * * * *', null, null); }).toThrow(); expect(() => { - new cron.CronTime('60 * * * *', null, null); + new CronTime('60 * * * *', null, null); }).toThrow(); }); it('should test out of range hour', () => { expect(() => { - new cron.CronTime('* -1 * * *', null, null); + new CronTime('* -1 * * *', null, null); }).toThrow(); expect(() => { - new cron.CronTime('* 24 * * *', null, null); + new CronTime('* 24 * * *', null, null); }).toThrow(); }); it('should test out of range day-of-month', () => { expect(() => { - new cron.CronTime('* * 0 * *', null, null); + new CronTime('* * 0 * *', null, null); }).toThrow(); expect(() => { - new cron.CronTime('* * 32 * *', null, null); + new CronTime('* * 32 * *', null, null); }).toThrow(); }); it('should test out of range month', () => { expect(() => { - new cron.CronTime('* * * 0 *', null, null); + new CronTime('* * * 0 *', null, null); }).toThrow(); expect(() => { - new cron.CronTime('* * * 13 *', null, null); + new CronTime('* * * 13 *', null, null); }).toThrow(); }); it('should test out of range day-of-week', () => { expect(() => { - new cron.CronTime('* * * * -1', null, null); + new CronTime('* * * * -1', null, null); }).toThrow(); expect(() => { - new cron.CronTime('* * * * 8', null, null); + new CronTime('* * * * 8', null, null); }).toThrow(); }); }); it('should test invalid wildcard expression', () => { expect(() => { - new cron.CronTime('* * * * 0*'); + new CronTime('* * * * 0*'); }).toThrow(); }); it('should test invalid step', () => { expect(() => { - new cron.CronTime('* * * 1/0 *'); + new CronTime('* * * 1/0 *'); }).toThrow(); }); it('should test invalid range', () => { expect(() => { - new cron.CronTime('* 2-1 * * *'); + new CronTime('* 2-1 * * *'); }).toThrow(); expect(() => { - new cron.CronTime('* 2-0 * * *'); + new CronTime('* 2-0 * * *'); }).toThrow(); expect(() => { - new cron.CronTime('* 2- * * *'); + new CronTime('* 2- * * *'); }).toThrow(); }); it('should test Date', () => { const d = new Date(); - const ct = new cron.CronTime(d); - expect(ct.source.toMillis()).toEqual(d.getTime()); + const ct = new CronTime(d); + expect(ct.source).toBeInstanceOf(DateTime); + expect((ct.source as DateTime).toMillis()).toEqual(d.getTime()); }); it('should test day roll-over', () => { const numHours = 24; - const ct = new cron.CronTime('0 0 17 * * *'); + const ct = new CronTime('0 0 17 * * *'); for (let hr = 0; hr < numHours; hr++) { const start = new Date(2012, 3, 16, hr, 30, 30); - const next = ct._getNextDateFrom(start); - expect(next - start).toBeLessThan(24 * 60 * 60 * 1000); + const next = ct.getNextDateFrom(start); + expect(next.toMillis() - start.getTime()).toBeLessThan( + 24 * 60 * 60 * 1000 + ); expect(next.toMillis()).toBeGreaterThan(start.getTime()); } }); it('should test illegal repetition syntax', () => { expect(() => { - new cron.CronTime('* * /4 * * *'); + new CronTime('* * /4 * * *'); }).toThrow(); }); it('should test next date', () => { - const ct = new cron.CronTime('0 0 */4 * * *'); + const ct = new CronTime('0 0 */4 * * *'); const nextDate = new Date(); nextDate.setHours(23); - const nextdt = ct._getNextDateFrom(nextDate); + const nextdt = ct.getNextDateFrom(nextDate); expect(nextdt.toMillis()).toBeGreaterThan(nextDate.getTime()); - expect(nextdt.hour % 4).toEqual(0); + expect(nextdt.hour % 4).toBe(0); }); it('should throw an exception because next date is invalid', () => { - const ct = new cron.CronTime('0 0 * * * *'); + const ct = new CronTime('0 0 * * * *'); const nextDate = new Date('My invalid date string'); expect(() => { - ct._getNextDateFrom(nextDate); + ct.getNextDateFrom(nextDate); }).toThrow('ERROR: You specified an invalid date.'); }); it('should test next real date', () => { const initialDate = new Date(); initialDate.setDate(initialDate.getDate() + 1); // In other case date will be in the past - const ct = new cron.CronTime(initialDate); + const ct = new CronTime(initialDate); const nextDate = new Date(); nextDate.setMonth(nextDate.getMonth() + 1); - expect(nextDate.getTime()).toBeGreaterThan(ct.source.toMillis()); - const nextdt = ct.sendAt(0); + expect(ct.source).toBeInstanceOf(DateTime); + expect(nextDate.getTime()).toBeGreaterThan( + (ct.source as DateTime).toMillis() + ); + const nextDt = ct.sendAt(); // there shouldn't be a "next date" when using a real date. // execution happens once // so the return should be the date passed in unless explicitly reset - expect(nextdt < nextDate).toBeTruthy(); - expect(nextdt.toMillis()).toEqual(initialDate.getTime()); + expect(nextDt.toMillis() < nextDate.getTime()).toBeTruthy(); + expect(nextDt.toMillis()).toEqual(initialDate.getTime()); }); describe('presets', () => { it('should parse @secondly', () => { - const cronTime = new cron.CronTime('@secondly'); - expect(cronTime.toString()).toEqual('* * * * * *'); + const cronTime = new CronTime('@secondly'); + expect(cronTime.toString()).toBe('* * * * * *'); }); it('should parse @minutely', () => { - const cronTime = new cron.CronTime('@minutely'); - expect(cronTime.toString()).toEqual('0 * * * * *'); + const cronTime = new CronTime('@minutely'); + expect(cronTime.toString()).toBe('0 * * * * *'); }); it('should parse @hourly', () => { - const cronTime = new cron.CronTime('@hourly'); - expect(cronTime.toString()).toEqual('0 0 * * * *'); + const cronTime = new CronTime('@hourly'); + expect(cronTime.toString()).toBe('0 0 * * * *'); }); it('should parse @daily', () => { - const cronTime = new cron.CronTime('@daily'); - expect(cronTime.toString()).toEqual('0 0 0 * * *'); + const cronTime = new CronTime('@daily'); + expect(cronTime.toString()).toBe('0 0 0 * * *'); }); it('should parse @weekly', () => { - const cronTime = new cron.CronTime('@weekly'); - expect(cronTime.toString()).toEqual('0 0 0 * * 0'); + const cronTime = new CronTime('@weekly'); + expect(cronTime.toString()).toBe('0 0 0 * * 0'); }); it('should parse @weekdays', () => { - const cronTime = new cron.CronTime('@weekdays'); - expect(cronTime.toString()).toEqual('0 0 0 * * 1,2,3,4,5'); + const cronTime = new CronTime('@weekdays'); + expect(cronTime.toString()).toBe('0 0 0 * * 1,2,3,4,5'); }); it('should parse @weekends', () => { - const cronTime = new cron.CronTime('@weekends'); - expect(cronTime.toString()).toEqual('0 0 0 * * 0,6'); + const cronTime = new CronTime('@weekends'); + expect(cronTime.toString()).toBe('0 0 0 * * 0,6'); }); it('should parse @monthly', () => { - const cronTime = new cron.CronTime('@monthly'); - expect(cronTime.toString()).toEqual('0 0 0 1 * *'); + const cronTime = new CronTime('@monthly'); + expect(cronTime.toString()).toBe('0 0 0 1 * *'); }); it('should parse @yearly', () => { - const cronTime = new cron.CronTime('@yearly'); - expect(cronTime.toString()).toEqual('0 0 0 1 1 *'); + const cronTime = new CronTime('@yearly'); + expect(cronTime.toString()).toBe('0 0 0 1 1 *'); }); }); describe('should throw an exception because `L` not supported', () => { it('(* * * L * *)', () => { expect(() => { - new cron.CronTime('* * * L * *'); + new CronTime('* * * L * *'); }).toThrow(); }); it('(* * * * * L)', () => { expect(() => { - new cron.CronTime('* * * * * L'); + new CronTime('* * * * * L'); }).toThrow(); }); }); it('should strip off millisecond', () => { - const cronTime = new cron.CronTime('0 */10 * * * *'); - const x = cronTime._getNextDateFrom(new Date('2018-08-10T02:20:00.999Z')); + const cronTime = new CronTime('0 */10 * * * *'); + const x = cronTime.getNextDateFrom(new Date('2018-08-10T02:20:00.999Z')); expect(x.toMillis()).toEqual( new Date('2018-08-10T02:30:00.000Z').getTime() ); }); it('should strip off millisecond (2)', () => { - const cronTime = new cron.CronTime('0 */10 * * * *'); - const x = cronTime._getNextDateFrom(new Date('2018-08-10T02:19:59.999Z')); + const cronTime = new CronTime('0 */10 * * * *'); + const x = cronTime.getNextDateFrom(new Date('2018-08-10T02:19:59.999Z')); expect(x.toMillis()).toEqual( new Date('2018-08-10T02:20:00.000Z').getTime() ); }); - it('should expose _getNextDateFrom as a public function', () => { - const cronTime = new cron.CronTime('0 */10 * * * *'); - cronTime._getNextDateFrom = jest.fn(); + it('should expose getNextDateFrom as a public function', () => { + const cronTime = new CronTime('0 */10 * * * *'); + cronTime.getNextDateFrom = jest.fn(); const testDate = new Date('2018-08-10T02:19:59.999Z'); const testTimezone = 'Asia/Amman'; cronTime.getNextDateFrom(testDate, testTimezone); - expect(cronTime._getNextDateFrom).toHaveBeenCalledWith( + expect(cronTime.getNextDateFrom).toHaveBeenCalledWith( testDate, testTimezone ); }); it('should generate the right next days when cron is set to every minute', () => { - const cronTime = new cron.CronTime('* * * * *'); + const cronTime = new CronTime('* * * * *'); const min = 60000; - let previousDate = new Date(Date.UTC(2018, 5, 3, 0, 0)); + let previousDate = DateTime.fromMillis(Date.UTC(2018, 5, 3, 0, 0)); for (let i = 0; i < 25; i++) { - const nextDate = cronTime._getNextDateFrom(previousDate); + const nextDate = cronTime.getNextDateFrom(previousDate); expect(nextDate.valueOf()).toEqual(previousDate.valueOf() + min); previousDate = nextDate; } }); it('should generate the right next days when cron is set to every 15 min', () => { - const cronTime = new cron.CronTime('*/15 * * * *'); + const cronTime = new CronTime('*/15 * * * *'); const min = 60000 * 15; - let previousDate = new Date(Date.UTC(2016, 6, 3, 0, 0)); + let previousDate = DateTime.fromMillis(Date.UTC(2016, 6, 3, 0, 0)); for (let i = 0; i < 25; i++) { - const nextDate = cronTime._getNextDateFrom(previousDate); + const nextDate = cronTime.getNextDateFrom(previousDate); expect(nextDate.valueOf()).toEqual(previousDate.valueOf() + min); previousDate = nextDate; } @@ -398,104 +407,121 @@ describe('crontime', () => { it('should work around time zone changes that shifts time back (1)', () => { const d = new Date('10-7-2018'); // America/Sao_Paulo has a time zone change around NOV 3 2018. - const cronTime = new cron.CronTime('0 0 9 4 * *'); - const nextDate = cronTime._getNextDateFrom(d, 'America/Sao_Paulo'); + const cronTime = new CronTime('0 0 9 4 * *'); + const nextDate = cronTime.getNextDateFrom(d, 'America/Sao_Paulo'); expect(nextDate.valueOf()).toEqual( - luxon.DateTime.fromISO('2018-11-04T09:00:00.000-02:00').valueOf() + DateTime.fromISO('2018-11-04T09:00:00.000-02:00').valueOf() ); }); it('should work around time zone changes that shifts time back (2)', () => { // Asia/Amman DST ends in 26 - OCT-2018 (-1 to hours) - const currentDate = luxon.DateTime.fromISO('2018-10-25T23:00', { + const currentDate = DateTime.fromISO('2018-10-25T23:00', { zone: 'Asia/Amman' }); - const cronTime = new cron.CronTime('0 0 * * *'); - const nextDate = cronTime._getNextDateFrom(currentDate, 'Asia/Amman'); - const expectedDate = luxon.DateTime.fromISO('2018-10-26T00:00+03:00', { + const cronTime = new CronTime('0 0 * * *'); + const nextDate = cronTime.getNextDateFrom(currentDate, 'Asia/Amman'); + const expectedDate = DateTime.fromISO('2018-10-26T00:00+03:00', { zone: 'Asia/Amman' }); - expect(nextDate - expectedDate).toEqual(0); + expect(nextDate.toMillis() - expectedDate.toMillis()).toBe(0); }); it('should work around time zone changes that shifts time forward', () => { // Asia/Amman DST starts in 30-March-2018 (+1 to hours) - let currentDate = luxon.DateTime.fromISO('2018-03-29T23:00', { + let currentDate = DateTime.fromISO('2018-03-29T23:00', { zone: 'Asia/Amman' }); - const cronTime = new cron.CronTime('* * * * *'); + const cronTime = new CronTime('* * * * *'); for (let i = 0; i < 100; i++) { - const nextDate = cronTime._getNextDateFrom(currentDate, 'Asia/Amman'); - expect(nextDate - currentDate).toEqual(1000 * 60); + const nextDate = cronTime.getNextDateFrom(currentDate, 'Asia/Amman'); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual(1000 * 60); currentDate = nextDate; } }); it('Should schedule jobs inside time zone changes that shifts time forward to the end of the shift, for weekly jobs', () => { - let currentDate = luxon.DateTime.fromISO('2018-03-29T23:15', { + let currentDate = DateTime.fromISO('2018-03-29T23:15', { zone: 'Asia/Amman' }); - const cronTime = new cron.CronTime('30 0 * * 5'); // the next 0:30 is March 30th, but it will jump from 0:00 to 1:00. - let nextDate = cronTime._getNextDateFrom(currentDate, 'Asia/Amman'); - expect(nextDate - currentDate).toEqual(1000 * 60 * 45); // 45 minutes is 30T00:00, which jumps to 1:00 which is past the trigger of 0:30. + const cronTime = new CronTime('30 0 * * 5'); // the next 0:30 is March 30th, but it will jump from 0:00 to 1:00. + let nextDate = cronTime.getNextDateFrom(currentDate, 'Asia/Amman'); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 60 * 45 + ); // 45 minutes is 30T00:00, which jumps to 1:00 which is past the trigger of 0:30. // the next one should just be at 0:30 again. i.e. a week minus 30 minutes. currentDate = nextDate; - nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(3600000 * 24 * 7 - 60000 * 30); + nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 3600000 * 24 * 7 - 60000 * 30 + ); // the next one is again at 0:30, but now we're 'back to normal' with weekly offsets. currentDate = nextDate; - nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 3600 * 24 * 7); + nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 3600 * 24 * 7 + ); }); it('Should schedule jobs inside time zone changes that shifts the time forward to the end of the shift, for daily jobs', () => { - let currentDate = luxon.DateTime.fromISO('2018-03-29T23:45', { + let currentDate = DateTime.fromISO('2018-03-29T23:45', { zone: 'Asia/Amman' }); - const cronTime = new cron.CronTime('30 0 * * *'); // the next 0:30 is March 30th, but it will jump from 0:00 to 1:00. - let nextDate = cronTime._getNextDateFrom(currentDate, 'Asia/Amman'); - expect(nextDate - currentDate).toEqual(1000 * 60 * 15); // 15 minutes is 30T00:00, which jumps to 1:00 which is past the trigger of 0:30. + const cronTime = new CronTime('30 0 * * *'); // the next 0:30 is March 30th, but it will jump from 0:00 to 1:00. + let nextDate = cronTime.getNextDateFrom(currentDate, 'Asia/Amman'); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 60 * 15 + ); // 15 minutes is 30T00:00, which jumps to 1:00 which is past the trigger of 0:30. // the next one is tomorrow at 0:30, so 23h30m. currentDate = nextDate; - nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 3600 * 24 - 1000 * 60 * 30); + nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 3600 * 24 - 1000 * 60 * 30 + ); // back to normal. currentDate = nextDate; - nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 3600 * 24); + nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 3600 * 24 + ); }); it('Should schedule jobs inside time zone changes that shifts the time forward to the end of the shift, for hourly jobs', () => { - let currentDate = luxon.DateTime.fromISO('2018-03-29T23:45', { + let currentDate = DateTime.fromISO('2018-03-29T23:45', { zone: 'Asia/Amman' }); - const cronTime = new cron.CronTime('30 * * * *'); // the next 0:30 is March 30th, but it will jump from 0:00 to 1:00. - let nextDate = cronTime._getNextDateFrom(currentDate, 'Asia/Amman'); - expect(nextDate - currentDate).toEqual(1000 * 60 * 15); // 15 minutes is 30T00:00, which jumps to 1:00 which is past the trigger of 0:30. + const cronTime = new CronTime('30 * * * *'); // the next 0:30 is March 30th, but it will jump from 0:00 to 1:00. + let nextDate = cronTime.getNextDateFrom(currentDate, 'Asia/Amman'); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 60 * 15 + ); // 15 minutes is 30T00:00, which jumps to 1:00 which is past the trigger of 0:30. // the next one is at 1:30, so 30m. currentDate = nextDate; - nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 60 * 30); + nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 60 * 30 + ); // back to normal. currentDate = nextDate; - nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 3600); + nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual(1000 * 3600); }); it('Should schedule jobs inside time zone changes that shifts the time forward to the end of the shift, for minutely jobs', () => { - let currentDate = luxon.DateTime.fromISO('2018-03-29T23:59', { + let currentDate = DateTime.fromISO('2018-03-29T23:59', { zone: 'Asia/Amman' }); - const cronTime = new cron.CronTime('* * * * *'); // the next minute is 0:00 is March 30th, but it will jump from 0:00 to 1:00. - let nextDate = cronTime._getNextDateFrom(currentDate, 'Asia/Amman'); - expect(nextDate - currentDate).toEqual(1000 * 60); + const cronTime = new CronTime('* * * * *'); // the next minute is 0:00 is March 30th, but it will jump from 0:00 to 1:00. + let nextDate = cronTime.getNextDateFrom(currentDate, 'Asia/Amman'); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual(1000 * 60); // the next one is at 1:01:00, this should still be 60 seconds in the future. currentDate = nextDate; - nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 60); + nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual(1000 * 60); }); // Do not think a similar test for secondly job is necessary, the minutely one already ensured no double hits in the overlap zone. it('Should throw when dates that are not within 24 hours of DST jumps are checked for past DST jumps', () => { - const cronTime = new cron.CronTime('* * * * *'); - const tryFindPastDST = isoTime => () => { - const maybeBadDate = luxon.DateTime.fromISO(isoTime, { + const cronTime = new CronTime('* * * * *'); + const tryFindPastDST = (isoTime: string) => () => { + const maybeBadDate = DateTime.fromISO(isoTime, { zone: 'Asia/Amman' }); - expect(maybeBadDate.invalid).toEqual(null); + expect(maybeBadDate.isValid).toBe(true); + // @ts-expect-error testing private property cronTime._findPreviousDSTJump(maybeBadDate); }; @@ -507,173 +533,190 @@ describe('crontime', () => { // the functions they are calling assume the given parameters encapsulate a DST jump, // and use the raw hour and minute data to check it from there. it('Should correctly scan time periods as if they are DST jumps, half hour jumps', () => { - let endDate = luxon.DateTime.fromISO('2023-01-01T16:00:00.000', { + let endDate = DateTime.fromISO('2023-01-01T16:00:00.000', { zone: 'Europe/Amsterdam' }); let startDate = endDate.minus({ minute: 30, second: 1 }); - const cronTime = new cron.CronTime('5 16 * * *'); // at 16:05:00. - let jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(false); + const cronTime = new CronTime('5 16 * * *'); // at 16:05:00. + // @ts-expect-error testing private property + let isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(false); endDate = endDate.plus({ minute: 30 }); // 16:30:00 startDate = endDate.minus({ minute: 30, second: 1 }); // 15:59:59 - jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(true); + // @ts-expect-error testing private property + isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(true); }); it('Should not include seconds in the minute after the DST jump as part of the jump scan', () => { - const endDate = luxon.DateTime.fromISO('2023-01-01T16:00:00.000', { + const endDate = DateTime.fromISO('2023-01-01T16:00:00.000', { zone: 'Europe/Amsterdam' }); // 1 hour jump case let startDate = endDate.minus({ hour: 1, second: 1 }); - const cronTime = new cron.CronTime('1 5 16 * * *'); // at 16:00:01. - let jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(false); + const cronTime = new CronTime('1 5 16 * * *'); // at 16:00:01. + // @ts-expect-error testing private property + let isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(false); // 'quirky' jump case startDate = endDate.minus({ hour: 1, minute: 45, second: 1 }); - jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(false); + // @ts-expect-error testing private property + isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(false); }); it('Should correctly scan time periods as if they are DST jumps, full hour jumps', () => { - let endDate = luxon.DateTime.fromISO('2023-01-01T16:00:00.000', { + let endDate = DateTime.fromISO('2023-01-01T16:00:00.000', { zone: 'Europe/Amsterdam' }); let startDate = endDate.minus({ hour: 1, second: 1 }); - const cronTime = new cron.CronTime('5 16 * * *'); // at 16:05:00. - let jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(false); + const cronTime = new CronTime('5 16 * * *'); // at 16:05:00. + // @ts-expect-error testing private property + let isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(false); endDate = endDate.plus({ hour: 1 }); // 17:00:00 startDate = endDate.minus({ hour: 1, second: 1 }); // 15:59:59 - jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(true); + // @ts-expect-error testing private property + isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(true); }); // A 'quirky' DST jump is one that should not break the implementation, but does not exist in real life (yet) it('Should correctly scan time periods as if they are DST jumps, quirky jumps (1)', () => { // Testing a jump that is less than an hour long, but wraps around an hour. - let endDate = luxon.DateTime.fromISO('2023-01-01T16:15:00.000', { + let endDate = DateTime.fromISO('2023-01-01T16:15:00.000', { zone: 'Europe/Amsterdam' }); let startDate = endDate.minus({ minute: 45, second: 1 }); // 15:29:59 - const cronTime = new cron.CronTime('30 16 * * *'); // at 16:30:00. - let jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(false); + const cronTime = new CronTime('30 16 * * *'); // at 16:30:00. + // @ts-expect-error testing private property + let isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(false); endDate = endDate.plus({ minute: 30 }); // 16:45:00 startDate = endDate.minus({ minute: 50, second: 1 }); // 15:54:59 - jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(true); + // @ts-expect-error testing private property + isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(true); }); it('Should correctly scan time periods as if they are DST jumps, quirky jumps (2)', () => { // Testing a jump that is over an hour long. - let endDate = luxon.DateTime.fromISO('2023-01-01T16:15:00.000', { + let endDate = DateTime.fromISO('2023-01-01T16:15:00.000', { zone: 'Europe/Amsterdam' }); let startDate = endDate.minus({ hour: 3, minute: 45, second: 1 }); // 12:29:59 - const cronTime = new cron.CronTime('30 16 * * *'); // at 16:30:00. - let jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(false); + const cronTime = new CronTime('30 16 * * *'); // at 16:30:00. + // @ts-expect-error testing private property + let isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(false); endDate = endDate.plus({ minute: 30 }); // 16:45:00 startDate = endDate.minus({ hour: 3, minute: 45, second: 1 }); // 12:59:59 - jobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); - expect(jobInRange).toEqual(true); + // @ts-expect-error testing private property + isJobInRange = cronTime._checkTimeInSkippedRange(startDate, endDate); + expect(isJobInRange).toBe(true); }); it('Enforces the hour difference assumption for handling multi-hour DST jumps', () => { - const cronTime = new cron.CronTime('30 16 * * *'); + const cronTime = new CronTime('30 16 * * *'); expect(() => { + // @ts-expect-error testing private property cronTime._checkTimeInSkippedRangeMultiHour(15, 0, 15, 30); }).toThrow(); }); it('should generate the right N next days for * * * * *', () => { - const cronTime = new cron.CronTime('* * * * *'); - let currentDate = luxon.DateTime.local().set({ second: 0, millisecond: 0 }); + const cronTime = new CronTime('* * * * *'); + let currentDate = DateTime.local().set({ second: 0, millisecond: 0 }); for (let i = 0; i < 100; i++) { - const nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 60); + const nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual(1000 * 60); currentDate = nextDate; } }); it('should generate the right N next days for 0 0 9 * * *', () => { - const cronTime = new cron.CronTime('0 0 9 * * *'); - let currentDate = luxon.DateTime.local() + const cronTime = new CronTime('0 0 9 * * *'); + let currentDate = DateTime.local() .setZone('utc') .set({ hour: 9, minute: 0, second: 0, millisecond: 0 }); for (let i = 0; i < 100; i++) { - const nextDate = cronTime._getNextDateFrom(currentDate); - expect(nextDate - currentDate).toEqual(1000 * 60 * 60 * 24); + const nextDate = cronTime.getNextDateFrom(currentDate); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 60 * 60 * 24 + ); currentDate = nextDate; } }); it('should generate the right N next days for 0 0 * * * with a time zone', () => { - const cronTime = new cron.CronTime('0 * * * *'); - let currentDate = luxon.DateTime.fromISO('2018-11-02T23:00', { + const cronTime = new CronTime('0 * * * *'); + let currentDate = DateTime.fromISO('2018-11-02T23:00', { zone: 'America/Sao_Paulo' }).set({ second: 0, millisecond: 0 }); for (let i = 0; i < 25; i++) { - const nextDate = cronTime._getNextDateFrom( + const nextDate = cronTime.getNextDateFrom( currentDate, 'America/Sao_Paulo' ); - expect(nextDate - currentDate).toEqual(1000 * 60 * 60); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 60 * 60 + ); currentDate = nextDate; } }); it('should generate the right N next days for */3 * * * * with a time zone', () => { - const cronTime = new cron.CronTime('*/3 * * * *'); - let currentDate = luxon.DateTime.fromISO('2018-11-02T23:00', { + const cronTime = new CronTime('*/3 * * * *'); + let currentDate = DateTime.fromISO('2018-11-02T23:00', { zone: 'America/Sao_Paulo' }).set({ second: 0, millisecond: 0 }); for (let i = 0; i < 25; i++) { - const nextDate = cronTime._getNextDateFrom( + const nextDate = cronTime.getNextDateFrom( currentDate, 'America/Sao_Paulo' ); - expect(nextDate - currentDate).toEqual(1000 * 60 * 3); + expect(nextDate.toMillis() - currentDate.toMillis()).toEqual( + 1000 * 60 * 3 + ); currentDate = nextDate; } }); it('should test valid range of months (*/15 * * 7-12 *)', () => { - const cronTime = new cron.CronTime('*/15 * * 7-12 *'); + const cronTime = new CronTime('*/15 * * 7-12 *'); const previousDate1 = new Date(Date.UTC(2018, 3, 0, 0, 0)); - const nextDate1 = cronTime._getNextDateFrom(previousDate1, 'UTC'); - expect(new Date(nextDate1).toUTCString()).toEqual( + const nextDate1 = cronTime.getNextDateFrom(previousDate1, 'UTC'); + expect(nextDate1.toJSDate().toUTCString()).toEqual( new Date(Date.UTC(2018, 6, 1, 0, 0)).toUTCString() ); const previousDate2 = new Date(Date.UTC(2018, 8, 0, 0, 0)); - const nextDate2 = cronTime._getNextDateFrom(previousDate2, 'UTC'); - expect(new Date(nextDate2).toUTCString()).toEqual( + const nextDate2 = cronTime.getNextDateFrom(previousDate2, 'UTC'); + expect(nextDate2.toJSDate().toUTCString()).toEqual( new Date(Date.UTC(2018, 8, 0, 0, 15)).toUTCString() ); }); it('should generate the right next day when cron is set to every 15 min in Feb', () => { - const cronTime = new cron.CronTime('*/15 * * FEB *'); + const cronTime = new CronTime('*/15 * * FEB *'); const previousDate = new Date(Date.UTC(2018, 3, 0, 0, 0)); - const nextDate = cronTime._getNextDateFrom(previousDate, 'UTC'); + const nextDate = cronTime.getNextDateFrom(previousDate, 'UTC'); expect(nextDate.valueOf()).toEqual( new Date(Date.UTC(2019, 1, 1, 0, 0)).valueOf() ); }); it('should generate the right next day when cron is set to both day of the month and day of the week (1)', () => { - const cronTime = new cron.CronTime('0 8 1 * 4'); + const cronTime = new CronTime('0 8 1 * 4'); const previousDate = new Date(Date.UTC(2019, 3, 21, 0, 0)); - const nextDate = cronTime._getNextDateFrom(previousDate, 'UTC'); + const nextDate = cronTime.getNextDateFrom(previousDate, 'UTC'); expect(nextDate.toMillis()).toEqual( new Date(Date.UTC(2019, 3, 25, 8, 0)).getTime() ); }); it('should generate the right next day when cron is set to both day of the month and day of the week (2)', () => { - const cronTime = new cron.CronTime('0 8 1 * 4'); + const cronTime = new CronTime('0 8 1 * 4'); const previousDate = new Date(Date.UTC(2019, 3, 26, 0, 0)); - const nextDate = cronTime._getNextDateFrom(previousDate, 'UTC'); + const nextDate = cronTime.getNextDateFrom(previousDate, 'UTC'); expect(nextDate.toMillis()).toEqual( new Date(Date.UTC(2019, 4, 1, 8, 0)).getTime() ); }); it('should generate the right next day when cron is set to both day of the month and day of the week (3)', () => { - const cronTime = new cron.CronTime('0 8 1 * 4'); + const cronTime = new CronTime('0 8 1 * 4'); const previousDate = new Date(Date.UTC(2019, 7, 1, 7, 59)); - const nextDate = cronTime._getNextDateFrom(previousDate, 'UTC'); + const nextDate = cronTime.getNextDateFrom(previousDate, 'UTC'); expect(nextDate.valueOf()).toEqual( new Date(Date.UTC(2019, 7, 1, 8, 0)).valueOf() ); @@ -682,8 +725,8 @@ describe('crontime', () => { it('should accept 0 as a valid UTC offset', () => { const clock = sinon.useFakeTimers(); - const cronTime = new cron.CronTime('0 11 * * *', null, 0); - const expected = luxon.DateTime.local().plus({ hours: 11 }).toSeconds(); + const cronTime = new CronTime('0 11 * * *', null, 0); + const expected = DateTime.local().plus({ hours: 11 }).toSeconds(); const actual = cronTime.sendAt().toSeconds(); expect(actual).toEqual(expected); @@ -694,8 +737,20 @@ describe('crontime', () => { it('should accept -120 as a valid UTC offset', () => { const clock = sinon.useFakeTimers(); - const cronTime = new cron.CronTime('0 11 * * *', null, -120); - const expected = luxon.DateTime.local().plus({ hours: 13 }).toSeconds(); + const cronTime = new CronTime('0 11 * * *', null, -120); + const expected = DateTime.local().plus({ hours: 13 }).toSeconds(); + const actual = cronTime.sendAt().toSeconds(); + + expect(actual).toEqual(expected); + + clock.restore(); + }); + + it('should accept 4 as a valid UTC offset', () => { + const clock = sinon.useFakeTimers(); + + const cronTime = new CronTime('0 11 * * *', null, 5); + const expected = DateTime.local().plus({ hours: 6 }).toSeconds(); const actual = cronTime.sendAt().toSeconds(); expect(actual).toEqual(expected); @@ -708,7 +763,7 @@ describe('crontime', () => { const d = new Date(); clock.tick(1000); - const time = new cron.CronTime(d); + const time = new CronTime(d); expect(() => { time.sendAt(); }).toThrow(); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..6c84955d --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "tests", "dist", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..15009def --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2015", + "module": "commonjs", + "outDir": "./dist", + "incremental": true, + "declaration": true, + "removeComments": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "noErrorTruncation": true, + "resolveJsonModule": true, + + /** + * strictest Typescript possible + */ + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitOverride": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strictPropertyInitialization": true + } +} diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index ddd58fa5..00000000 --- a/types/index.d.ts +++ /dev/null @@ -1,181 +0,0 @@ -// Type definitions for cron 2.0 -// Project: https://www.npmjs.com/package/cron -// Definitions by: Hiroki Horiuchi -// Lundarl Gholoi -// koooge -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -/// - -import { SpawnOptions } from 'child_process'; -import { DateTime } from 'luxon'; - -export declare type CronCommand = - | (() => void) - | string - | { - command: string; - args?: ReadonlyArray | undefined; - options?: SpawnOptions | undefined; - }; - -export declare class CronTime { - /** - * Create a new ```CronTime```. - * @param source The time to fire off your job. This can be in the form of cron syntax, a JS ```Date``` object, or a luxon ```DateTime``` object. - * @param zone Timezone name. Can be any string accepted by luxon's ```DateTime.setZone()``` (https://moment.github.io/luxon/api-docs/index.html#datetimesetzone). - * @param utcOffset UTC offset. Don't use both ```zone``` and ```utcOffset``` together or weird things may happen. - */ - constructor( - source: string | Date | DateTime, - zone?: string, - utcOffset?: string | number - ); - - /** - * Tells you when ```CronTime``` will be run. - */ - public sendAt(): DateTime; - /** - * Tells you when ```CronTime``` will be run. - * @param i Indicate which turn of run after now. If not given return next run time. - * @returns A ```DateTime``` when the source passed in the constructor is a ```Date``` or a ```DateTime``` and an array of ```DateTime``` when the source is a string. - */ - public sendAt(i?: number): DateTime | DateTime[]; - /** - * Get the number of milliseconds in the future at which to fire our callbacks. - */ - public getTimeout(): number; -} - -export declare interface CronJobParameters { - /** - * The time to fire off your job. This can be in the form of cron syntax, a JS ```Date``` object, or a luxon ```DateTime``` object. - */ - cronTime: string | Date | DateTime; - /** - * The function to fire at the specified time. If an ```onComplete``` callback was provided, ```onTick``` will receive it as an argument. ```onTick``` may call ```onComplete``` when it has finished its work. - */ - onTick: CronCommand; - /** - * A function that will fire when the job is stopped with ```job.stop()```, and may also be called by ```onTick``` at the end of each run. - */ - onComplete?: CronCommand | null | undefined; - /** - * Specifies whether to start the job just before exiting the constructor. By default this is set to false. If left at default you will need to call ```job.start()``` in order to start the job (assuming ```job``` is the variable you set the cronjob to). This does not immediately fire your ```onTick``` function, it just gives you more control over the behavior of your jobs. - */ - start?: boolean | undefined; - /** - * Specify the timezone for the execution. This will modify the actual time relative to your timezone. If the timezone is invalid, an error is thrown. Can be any string accepted by luxon's ```DateTime.setZone()``` (https://moment.github.io/luxon/api-docs/index.html#datetimesetzone). Probably don't use both ```timeZone``` and ```utcOffset``` together or weird things may happen. - */ - timeZone?: string | undefined; - /** - * The context within which to execute the onTick method. This defaults to the cronjob itself allowing you to call ```this.stop()```. However, if you change this you'll have access to the functions and values within your context object. - */ - context?: any; - /** - * This will immediately fire your ```onTick``` function as soon as the requisit initialization has happened. This option is set to ```false``` by default for backwards compatibility. - */ - runOnInit?: boolean | undefined; - /** - * This allows you to specify the offset of your timezone rather than using the ```timeZone``` param. Probably don't use both ```timeZone``` and ```utcOffset``` together or weird things may happen. - */ - utcOffset?: string | number | undefined; - /** - * If you have code that keeps the event loop running and want to stop the node process when that finishes regardless of the state of your cronjob, you can do so making use of this parameter. This is off by default and cron will run as if it needs to control the event loop. For more information take a look at [timers#timers_timeout_unref](https://nodejs.org/api/timers.html#timers_timeout_unref) from the NodeJS docs. - */ - unrefTimeout?: boolean | undefined; -} - -export declare class CronJob { - /** - * Return ```true``` if job is running. - */ - public running: boolean | undefined; - /** - * Function using to fire ```onTick```, default set to an inner private function. Overwrite this only if you have a really good reason to do so. - */ - public fireOnTick: Function; - - /** - * Create a new ```CronJob```. - * @param cronTime The time to fire off your job. This can be in the form of cron syntax or a JS ```Date``` object. - * @param onTick The function to fire at the specified time. - * @param onComplete A function that will fire when the job is complete, when it is stopped. - * @param startNow Specifies whether to start the job just before exiting the constructor. By default this is set to false. If left at default you will need to call ```job.start()``` in order to start the job (assuming ```job``` is the variable you set the cronjob to). This does not immediately fire your onTick function, it just gives you more control over the behavior of your jobs. - * @param timeZone Specify the timezone for the execution. This will modify the actual time relative to your timezone. If the timezone is invalid, an error is thrown. Can be any string accepted by luxon's ```DateTime.setZone()``` (https://moment.github.io/luxon/api-docs/index.html#datetimesetzone). - * @param context The context within which to execute the onTick method. This defaults to the cronjob itself allowing you to call ```this.stop()```. However, if you change this you'll have access to the functions and values within your context object. - * @param runOnInit This will immediately fire your ```onTick``` function as soon as the requisit initialization has happened. This option is set to ```false``` by default for backwards compatibility. - * @param utcOffset This allows you to specify the offset of your timezone rather than using the ```timeZone``` param. Probably don't use both ```timeZone``` and ```utcOffset``` together or weird things may happen. - * @param unrefTimeout If you have code that keeps the event loop running and want to stop the node process when that finishes regardless of the state of your cronjob, you can do so making use of this parameter. This is off by default and cron will run as if it needs to control the event loop. For more information take a look at [timers#timers_timeout_unref](https://nodejs.org/api/timers.html#timers_timeout_unref) from the NodeJS docs. - */ - constructor( - cronTime: string | Date | DateTime, - onTick: CronCommand, - onComplete?: CronCommand | null, - startNow?: boolean, - timeZone?: string, - context?: any, - runOnInit?: boolean, - utcOffset?: string | number, - unrefTimeout?: boolean - ); - /** - * Create a new ```CronJob```. - * @param options Job parameters. - */ - constructor(options: CronJobParameters); - - /** - * Runs your job. - */ - public start(): void; - /** - * Stops your job. - */ - public stop(): void; - /** - * Change the time for the ```CronJob```. - * @param time Target time. - */ - public setTime(time: CronTime): void; - /** - * Tells you the last execution date. - */ - public lastDate(): Date; - /** - * Tells you when a ```CronTime``` will be run. - */ - public nextDate(): DateTime; - public nextDates(): DateTime; - /** - * Tells you when a ```CronTime``` will be run. - * @param i Indicate which turn of run after now. If not given return next run time. - * @returns A `DateTime` when the cronTime passed in the constructor is a `Date` or a `DateTime` and an array of `DateTime` when the cronTime is a string. - */ - public nextDates(i?: number): DateTime | DateTime[]; - /** - * Add another ```onTick``` function. - * @param callback Target function. - */ - public addCallback(callback: Function): void; -} - -export declare function job(options: CronJobParameters): CronJob; -export declare function job( - cronTime: string | Date | DateTime, - onTick: () => void, - onComplete?: CronCommand | null, - start?: boolean, - timeZone?: string, - context?: any, - runOnInit?: boolean, - utcOffset?: string | number, - unrefTimeout?: boolean -): CronJob; -export declare function time( - source: string | Date | DateTime, - zone?: string -): CronTime; -export declare function sendAt(cronTime: string | Date | DateTime): DateTime; -export declare function timeout(cronTime: string | Date | DateTime): number; diff --git a/types/index.test-d.ts b/types/index.test-d.ts deleted file mode 100644 index 7e5f29b1..00000000 --- a/types/index.test-d.ts +++ /dev/null @@ -1,85 +0,0 @@ -import * as luxon from 'luxon'; -import { expectType } from 'tsd'; -import * as cron from '..'; - -const CronJob = cron.CronJob; -const CronTime = cron.CronTime; - -const DateTime = luxon.DateTime; - -const timeZone = 'America/Los_Angeles'; - -// basic cron usage -const job = new CronJob( - '* * * * * *', - () => {}, - null, - true -); - -// using factory function with parameters object -expectType(cron.job({ - cronTime: '00 30 11 * * 1-5', - onTick: () => {}, - start: false, - timeZone: 'America/Los_Angeles' -})); - -// with timezone -new CronJob( - '* * * * * *', - () => {}, - null, - true, - timeZone -); - -// with onComplete -new CronJob( - '00 30 11 * * 1-5', - () => {}, - () => {}, - true, - timeZone -); - -// with Date -new CronJob( - new Date(), - () => {}, - null, - true, - timeZone -); - -// with Luxon DateTime -new CronJob( - DateTime.local(), - () => {}, - null, - true, - timeZone -); - -// with system commands -new CronJob( - '00 30 11 * * 1-5', - 'ls', - { command: 'ls', args: ['./'] }, - true, - timeZone -); - -// check function return types -expectType(job.lastDate()); -expectType(job.nextDates()); -expectType(job.nextDates(1)); -expectType(job.running); -job.setTime(new CronTime('00 30 11 * * 1-2')); -job.start(); -job.stop(); - -// check cronTime format -new CronTime('* * * * * *'); -new CronTime(new Date()); -new CronTime(DateTime.local());