├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── data-error ├── KLOT_NSS_2022_01_16_09_31 └── KLTX_NSS_2022_02_15_06_44 ├── data ├── JAX_NST_2021_04_11_19_37_00 ├── KBYX_NSS_2022_01_16_09_31 ├── KCAE_NSS_2022_01_16_09_31 ├── KDGX_NST_2022_01_16_09_31 ├── KFFC_NSS_2022_01_16_09_31 ├── LOT_DAA_2021_02_28_12_14_47 ├── LOT_DAA_2021_05_08_03_40_29 ├── LOT_DTA_2021_02_28_15_05_33 ├── LOT_DTA_2021_05_08_03_47_25 ├── LOT_HHC_2021_01_31_11_06_30 ├── LOT_N0H_2021_01_31_11_06_30 ├── LOT_N0S_2021_01_31_11_06_30 ├── LOT_N1P_2021_01_31_11_06_30 ├── LOT_NMD_2021_06_21_04_22_17 ├── LOT_NMD_2021_06_21_04_27_31 ├── LOT_NTP_2021_01_31_11_06_30 ├── SDUS53 KLOT 150709 ├── SRX_NST_2021_03_25_02_54_45 ├── TBW_NHI_2021_04_19_19_02 ├── TBW_NSS_2021_04_19_19_02 ├── TBW_NST_2021_04_19_19_02 ├── sn.0011 └── sn.0012 ├── output └── .gitignore ├── package-lock.json ├── package.json ├── src ├── headers │ ├── graphic.js │ ├── graphic22.js │ ├── message.js │ ├── productdescription.js │ ├── radialpackets.js │ ├── symbology.js │ ├── symbologytext.js │ ├── tabular.js │ └── text.js ├── index.js ├── packets │ ├── 1.js │ ├── 10.js │ ├── 13.js │ ├── 14.js │ ├── 15.js │ ├── 16.js │ ├── 17.js │ ├── 18.js │ ├── 19.js │ ├── 2.js │ ├── 32.js │ ├── 6.js │ ├── 8.js │ ├── a.js │ ├── af1f.js │ ├── c.js │ ├── f.js │ ├── index.js │ └── utilities │ │ ├── ij.js │ │ └── rle.js ├── products │ ├── 56 │ │ └── index.js │ ├── 58 │ │ ├── formatter.js │ │ └── index.js │ ├── 59 │ │ ├── formatter.js │ │ └── index.js │ ├── 61 │ │ ├── formatter.js │ │ └── index.js │ ├── 62 │ │ └── index.js │ ├── 78 │ │ └── index.js │ ├── 80 │ │ └── index.js │ ├── 94 │ │ └── index.js │ ├── 141 │ │ ├── formatter.js │ │ └── index.js │ ├── 165 │ │ └── index.js │ ├── 170 │ │ └── index.js │ ├── 172 │ │ └── index.js │ ├── 177 │ │ └── index.js │ └── index.js └── randomaccessfile │ └── index.js ├── test.js ├── testoutput.js └── workspace.code-workspace /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | commonjs: true, 5 | node: true, 6 | }, 7 | extends: 'airbnb-base', 8 | globals: { 9 | Atomics: 'readonly', 10 | SharedArrayBuffer: 'readonly', 11 | }, 12 | parserOptions: { 13 | ecmaVersion: 2020, 14 | }, 15 | rules: { 16 | indent: [ 17 | 'error', 18 | 'tab', 19 | ], 20 | 'no-use-before-define': [ 21 | 'error', 22 | { 23 | variables: false, 24 | }, 25 | ], 26 | 'no-param-reassign': [ 27 | 'error', 28 | { 29 | props: false, 30 | }, 31 | ], 32 | 'no-tabs': 0, 33 | 'max-len': 0, 34 | 'no-bitwise': 0, 35 | }, 36 | ignorePatterns: [ 37 | '*.min.js', 38 | ], 39 | }; 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | data 3 | data-error 4 | output 5 | *.code-workspace 6 | test.js 7 | testoutput.js 8 | .eslint* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/test.js" 15 | }, 16 | { 17 | "name": "Test All", 18 | "program": "${workspaceFolder}/testoutput.js", 19 | "request": "launch", 20 | "skipFiles": [ 21 | "/**" 22 | ], 23 | "type": "node" 24 | }, 25 | ] 26 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.6.0 2 | - Changed all messages logged to console.warn(), or the matching custom logger if provided. 3 | 4 | # v0.4.0 5 | - Handle multiple packets per layer. The .radialPackets structure will now return layers that have multiple packets as an array (typically text) or a single object when there is only one packet. 6 | # v0.3.0 7 | ## Breaking Changes 8 | - Flatten graphic header to an array of packets and omit page and length values 9 | 10 | # v0.2.0 11 | ## Breaking Changes 12 | - Parsers no longer return delimeters, block lengths, or IDs as these are internally evaluated and checked during the parsing process -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Matt Walsh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nexrad-level-3-data 2 | 3 | A javascript implementation for decoding Nexrad Level III radar files. 4 | 5 | You can find more information on how radar data is encoded at [NOAA](https://www.roc.noaa.gov/WSR88D/BuildInfo/Files.aspx). The work in this project is based mainly on the document [2620001 ICD FOR THE RPG TO CLASS 1 USER - Build 19.0](https://www.roc.noaa.gov/wsr88d/PublicDocs/ICDs/2620001Y.pdf). 6 | 7 | # Demo 8 | A live demo showing the output of this library (via [nexrad-level-3-plot](https://www.github.com/netbymatt/nexrad-level-3-plot)) for select radar sites is available at https://nexrad-demo.netbymatt.com/ 9 | 10 | ## Contents 11 | 1. [Install](#install) 12 | 1. [Usage](#usage) 13 | 1. [Examples](#examples) 14 | 1. [Testing](#testing) 15 | 1. [API](#api) 16 | 1. [Background Information](#background-information) 17 | 1. [Work In Progress](#work-in-progress) 18 | 1. [ToDo](#todo) 19 | 1. [Acknowledgements](#acknowledgements) 20 | 21 | ## Install 22 | 23 | ``` bash 24 | $ npm install nexrad-level-3-data 25 | ``` 26 | 27 | ## Usage 28 | Usage is straight forward, provide a string or buffer containing any of the [available products](#available-products). 29 | ``` javascript 30 | const fs = require('fs'); 31 | const parser = require('nexrad-level-3-data'); 32 | 33 | const file = fs.readFileSync('./data/LOT_N0H_2021_01_31_11_06_30'); 34 | const level3Data = parser(file); 35 | 36 | console.log(level3Data); 37 | ``` 38 | 39 | ## Examples 40 | Data and corresponding output are provided for quick and easy testing and experimentation. Please see the ```./data``` and ```./output``` folders. 41 | 42 | An example of plotting data produced from this library can be found in [nexrad-level-3-plot](https://github.com/netbymatt/nexrad-level-3-plot) 43 | 44 | ## Testing 45 | A test script is provided and will generate ```.json``` output from all of the files in the ```./data``` and ```./data-error``` folder. 46 | ``` 47 | npm test 48 | ``` 49 | A successful test will generate several ```.json``` files in the ```./output``` folder. Errors will be logged to the console for the data which has intentional errors, but the data that can be parsed will still be returned from each file. 50 | 51 | ## API 52 | 53 | The parser will read files as either a string or a buffer. Streams are not supported. The parser will return an object representing the data as defined in https://www.roc.noaa.gov/wsr88d/PublicDocs/ICDs/2620001Y.pdf 54 | 55 | In some situations additional data is provided in the productDescription property including a friendly name for the type of data or product legends to aid in decoding the data. Values are automatically parsed as integers, floats and strings as defined in the specification. 56 | 57 | The parser makes some rudimentary checks to confirm data is valid. These checks include: 58 | - Testing for the correct value for known separators and other fixed values in the specification. 59 | - Testing offsets for locations outside of the file provided 60 | 61 | The parser will also return rich error where possible indicating that a specific product or packet type is not supported. 62 | 63 | ```javascript 64 | parser(file, { 65 | // options shown as defaults 66 | logger: console, 67 | }) 68 | ``` 69 | 70 | ### file 71 | File can be a buffer or string containing the data from one of the [available products](#available-products). 72 | 73 | ### options.logger 74 | By default all messages are logged to the console. Messages can be surpressed by passing ``false``. A custom logger such as winston or pino can be provided. Any logger provided must provide the ``log()``, ``warn()`` and ``error()`` functions. 75 | 76 | ## Background Information 77 | Nexrad radar sites provide data in two formats: Level 2 and Level 3. Level 1 is used internally by the system and converted to Level 2 which is made available to end users. These products are detailed at https://www.ncdc.noaa.gov/data-access/radar-data/nexrad-products 78 | 79 | ### Level 2 Data 80 | Level 2 data can be considered raw data from the radar and provides the "Reflectivity" products that you're used to seeing on web sites and the local news. It also provides velocity data and several more advanced data outputs. 81 | 82 | ### Level 3 Data 83 | Level 3 data combines Level 2 data to provide additional information about the precipitation and air around the radar site. Some easier to understand forms of this data include: 84 | 85 | - Precipitation type, known as the Hydrometeor classification (Products N0H, N1H, N2H, N3H, packet code 165) 86 | - Various precipitation totals over 1-hour, 3-hour and storm duration. (Products N1P, N3P, NTP, OHA, DAA, PTA, DTA, packet codes 78, 79, 80, 169, 170, 171, 172) 87 | - Storm relative velocity (N0S, N1S, N2S, N3S packet code 56) 88 | 89 | Level 3 data is available in real-time through the AWS S3 bucket s3://unidata-nexrad-level3/ 90 | 91 | Additional documentation on the AWS bucket is available at https://registry.opendata.aws/noaa-nexrad/ 92 | 93 | The file naming convention is: SSS_PPP_YYYY_MM_DD_HH_MM_SS 94 | | Code | Meaning | 95 | |---|---| 96 | | SSS | Radar site ICAO without leading K ([List](https://www.roc.noaa.gov/WSR88D/Maps.aspx)) | 97 | | PPP | Product code [List](https://www.ncdc.noaa.gov/data-access/radar-data/nexrad-products)| 98 | | YYYY | 4-digit year | 99 | | MM | 2-digit month | 100 | | DD | 2-digit day | 101 | | HH | 2-digit, 24-hour hour | 102 | | MM | 2-digit minute | 103 | | SS | 2-digit seconds | 104 | 105 | ## Work In Progress 106 | I've developed parsing algorithms for that the products that I needed most for my own use. I'm open to requests or pull requests that add additional parsing algorithms. Available products are listed below. 107 | ### Available Products 108 | |ID|Product Code(s)|Description| 109 | |---|---|---| 110 | |56|N0S, N1S, N2S, N3S|Storm relative velocity| 111 | |58|NST|Storm Tracking Information| 112 | |59|NHI|Hail Index| 113 | |61|NTV|Tornadic Vortex Signature| 114 | |62|NSS|Storm Structure| 115 | |78|N1P|One-hour precipitation| 116 | |80|NTP|Storm Total Rainfall Accumulation| 117 | |94|NXQ, ...|Digital Base Reflectivity| 118 | |141|NMD|Mesocyclone| 119 | |165|N0H, N1H, N2H, N3H|Hydrometeor Classification 120 | |170|DAA|Digital One Hour Accumulation 121 | |172|DTA|Storm Total Precipitation 122 | |177|HHC|Hybrid Hydrometeor Classification 123 | ### Supported Packet Types 124 | |Code|Description| 125 | |---|---| 126 | |0x0001|Text and Special Symbol Packets| 127 | |0x0002|Text and Special Symbol Packets| 128 | |0x0006|Linked Vector Packet| 129 | |0x0008|Unlinked Vector Packet| 130 | |0x000F|Special Graphic Symbol Packet| 131 | |0x0010|Digital Radial Data Array Packet| 132 | |0x0012|Tornado Vortex Signature| 133 | |0x0013|Special Graphic Symbol Packet| 134 | |0x0015|Special Graphic Symbol Packet| 135 | |0x0016|Cell Trend Data Packet| 136 | |0x0017|Special Graphic Symbol Packet| 137 | |0x0018|Special Graphic Symbol Packet| 138 | |0x0019|Special Graphic Symbol Packet| 139 | |0x0020|Special Graphic Symbol Packet| 140 | |0xAF1F|Radial Data Packet (16 Data Levels)| 141 | 142 | ## A note about source data completeness 143 | It has been observed many times that the source data is occasionally incomplete. Because the data is made up of a header containing pointers to other parts of the file. Any individual block may be corrupt, but this code will continue to try the additional pointers. Errors like this are sent to console.warn() 144 | 145 | ## Possible Issues 146 | - In production it has been found that some NSS data includes symbology IDs other than 1 which is the only one that appears in the documentation. I've examined these other responses and have found these to be structured text. This text is returned as symbology.pages, but is technically undocumented per the specification above. 147 | - In production NSS data that is truncated has been found. Other cases of truncated data may also exist. The parser is designed to catch these errors, log them to the console and they do any additional processing that may be necessary. The recommended practice is to test that your desired data structure exists in the returned object before doing any further processing with it. 148 | 149 | ## ToDo 150 | * Add support for additional radial products 151 | * Add support for raster products 152 | + Categorize messages generated by node, such as reading past the end of the buffer as errors, and log them as errors. 153 | 154 | ## Acknowledgements 155 | The code for this project is based upon: 156 | - [Unidata](https://github.com/Unidata/thredds/blob/master/cdm/src/main/java/ucar/nc2/iosp/nexrad2/) 157 | - [nexrad-radar-data](https://github.com/bartholomew91/nexrad-radar-data) 158 | - And my own fork of the above [netbymatt/nexrad-radar-data](https://github.com/netbymatt/nexrad-radar-data) -------------------------------------------------------------------------------- /data-error/KLOT_NSS_2022_01_16_09_31: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data-error/KLOT_NSS_2022_01_16_09_31 -------------------------------------------------------------------------------- /data-error/KLTX_NSS_2022_02_15_06_44: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data-error/KLTX_NSS_2022_02_15_06_44 -------------------------------------------------------------------------------- /data/JAX_NST_2021_04_11_19_37_00: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/JAX_NST_2021_04_11_19_37_00 -------------------------------------------------------------------------------- /data/KBYX_NSS_2022_01_16_09_31: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/KBYX_NSS_2022_01_16_09_31 -------------------------------------------------------------------------------- /data/KCAE_NSS_2022_01_16_09_31: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/KCAE_NSS_2022_01_16_09_31 -------------------------------------------------------------------------------- /data/KDGX_NST_2022_01_16_09_31: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/KDGX_NST_2022_01_16_09_31 -------------------------------------------------------------------------------- /data/KFFC_NSS_2022_01_16_09_31: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/KFFC_NSS_2022_01_16_09_31 -------------------------------------------------------------------------------- /data/LOT_DAA_2021_02_28_12_14_47: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_DAA_2021_02_28_12_14_47 -------------------------------------------------------------------------------- /data/LOT_DAA_2021_05_08_03_40_29: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_DAA_2021_05_08_03_40_29 -------------------------------------------------------------------------------- /data/LOT_DTA_2021_02_28_15_05_33: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_DTA_2021_02_28_15_05_33 -------------------------------------------------------------------------------- /data/LOT_DTA_2021_05_08_03_47_25: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_DTA_2021_05_08_03_47_25 -------------------------------------------------------------------------------- /data/LOT_HHC_2021_01_31_11_06_30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_HHC_2021_01_31_11_06_30 -------------------------------------------------------------------------------- /data/LOT_N0H_2021_01_31_11_06_30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_N0H_2021_01_31_11_06_30 -------------------------------------------------------------------------------- /data/LOT_N0S_2021_01_31_11_06_30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_N0S_2021_01_31_11_06_30 -------------------------------------------------------------------------------- /data/LOT_N1P_2021_01_31_11_06_30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_N1P_2021_01_31_11_06_30 -------------------------------------------------------------------------------- /data/LOT_NMD_2021_06_21_04_22_17: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_NMD_2021_06_21_04_22_17 -------------------------------------------------------------------------------- /data/LOT_NMD_2021_06_21_04_27_31: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_NMD_2021_06_21_04_27_31 -------------------------------------------------------------------------------- /data/LOT_NTP_2021_01_31_11_06_30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/LOT_NTP_2021_01_31_11_06_30 -------------------------------------------------------------------------------- /data/SDUS53 KLOT 150709: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/SDUS53 KLOT 150709 -------------------------------------------------------------------------------- /data/SRX_NST_2021_03_25_02_54_45: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/SRX_NST_2021_03_25_02_54_45 -------------------------------------------------------------------------------- /data/TBW_NHI_2021_04_19_19_02: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/TBW_NHI_2021_04_19_19_02 -------------------------------------------------------------------------------- /data/TBW_NSS_2021_04_19_19_02: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/TBW_NSS_2021_04_19_19_02 -------------------------------------------------------------------------------- /data/TBW_NST_2021_04_19_19_02: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/TBW_NST_2021_04_19_19_02 -------------------------------------------------------------------------------- /data/sn.0011: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/sn.0011 -------------------------------------------------------------------------------- /data/sn.0012: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbymatt/nexrad-level-3-data/ee505f233762dbbd053d71094aea2345fb622162/data/sn.0012 -------------------------------------------------------------------------------- /output/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory as it is generated dynamically 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexrad-level-3-data", 3 | "version": "0.6.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nexrad-level-3-data", 9 | "version": "0.6.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "seek-bzip": "^2.0.0" 13 | }, 14 | "devDependencies": { 15 | "eslint": "^8.0.0", 16 | "eslint-config-airbnb-base": "^15.0.0", 17 | "eslint-plugin-import": "^2.22.1" 18 | }, 19 | "engines": { 20 | "node": ">=13.0.0" 21 | } 22 | }, 23 | "node_modules/@eslint-community/eslint-utils": { 24 | "version": "4.4.0", 25 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", 26 | "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", 27 | "dev": true, 28 | "license": "MIT", 29 | "dependencies": { 30 | "eslint-visitor-keys": "^3.3.0" 31 | }, 32 | "engines": { 33 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 34 | }, 35 | "peerDependencies": { 36 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 37 | } 38 | }, 39 | "node_modules/@eslint-community/regexpp": { 40 | "version": "4.10.0", 41 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", 42 | "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", 43 | "dev": true, 44 | "license": "MIT", 45 | "engines": { 46 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 47 | } 48 | }, 49 | "node_modules/@eslint/eslintrc": { 50 | "version": "2.1.4", 51 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", 52 | "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", 53 | "dev": true, 54 | "license": "MIT", 55 | "dependencies": { 56 | "ajv": "^6.12.4", 57 | "debug": "^4.3.2", 58 | "espree": "^9.6.0", 59 | "globals": "^13.19.0", 60 | "ignore": "^5.2.0", 61 | "import-fresh": "^3.2.1", 62 | "js-yaml": "^4.1.0", 63 | "minimatch": "^3.1.2", 64 | "strip-json-comments": "^3.1.1" 65 | }, 66 | "engines": { 67 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 68 | }, 69 | "funding": { 70 | "url": "https://opencollective.com/eslint" 71 | } 72 | }, 73 | "node_modules/@eslint/js": { 74 | "version": "8.57.0", 75 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", 76 | "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", 77 | "dev": true, 78 | "license": "MIT", 79 | "engines": { 80 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 81 | } 82 | }, 83 | "node_modules/@humanwhocodes/config-array": { 84 | "version": "0.11.14", 85 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", 86 | "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", 87 | "dev": true, 88 | "license": "Apache-2.0", 89 | "dependencies": { 90 | "@humanwhocodes/object-schema": "^2.0.2", 91 | "debug": "^4.3.1", 92 | "minimatch": "^3.0.5" 93 | }, 94 | "engines": { 95 | "node": ">=10.10.0" 96 | } 97 | }, 98 | "node_modules/@humanwhocodes/module-importer": { 99 | "version": "1.0.1", 100 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 101 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 102 | "dev": true, 103 | "license": "Apache-2.0", 104 | "engines": { 105 | "node": ">=12.22" 106 | }, 107 | "funding": { 108 | "type": "github", 109 | "url": "https://github.com/sponsors/nzakas" 110 | } 111 | }, 112 | "node_modules/@humanwhocodes/object-schema": { 113 | "version": "2.0.3", 114 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", 115 | "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", 116 | "dev": true, 117 | "license": "BSD-3-Clause" 118 | }, 119 | "node_modules/@nodelib/fs.scandir": { 120 | "version": "2.1.5", 121 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 122 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 123 | "dev": true, 124 | "license": "MIT", 125 | "dependencies": { 126 | "@nodelib/fs.stat": "2.0.5", 127 | "run-parallel": "^1.1.9" 128 | }, 129 | "engines": { 130 | "node": ">= 8" 131 | } 132 | }, 133 | "node_modules/@nodelib/fs.stat": { 134 | "version": "2.0.5", 135 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 136 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 137 | "dev": true, 138 | "license": "MIT", 139 | "engines": { 140 | "node": ">= 8" 141 | } 142 | }, 143 | "node_modules/@nodelib/fs.walk": { 144 | "version": "1.2.8", 145 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 146 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 147 | "dev": true, 148 | "license": "MIT", 149 | "dependencies": { 150 | "@nodelib/fs.scandir": "2.1.5", 151 | "fastq": "^1.6.0" 152 | }, 153 | "engines": { 154 | "node": ">= 8" 155 | } 156 | }, 157 | "node_modules/@types/json5": { 158 | "version": "0.0.29", 159 | "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", 160 | "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", 161 | "dev": true, 162 | "license": "MIT" 163 | }, 164 | "node_modules/@ungap/structured-clone": { 165 | "version": "1.2.0", 166 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", 167 | "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", 168 | "dev": true, 169 | "license": "ISC" 170 | }, 171 | "node_modules/acorn": { 172 | "version": "8.11.3", 173 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 174 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 175 | "dev": true, 176 | "license": "MIT", 177 | "bin": { 178 | "acorn": "bin/acorn" 179 | }, 180 | "engines": { 181 | "node": ">=0.4.0" 182 | } 183 | }, 184 | "node_modules/acorn-jsx": { 185 | "version": "5.3.2", 186 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 187 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 188 | "dev": true, 189 | "license": "MIT", 190 | "peerDependencies": { 191 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 192 | } 193 | }, 194 | "node_modules/ajv": { 195 | "version": "6.12.6", 196 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 197 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 198 | "dev": true, 199 | "license": "MIT", 200 | "dependencies": { 201 | "fast-deep-equal": "^3.1.1", 202 | "fast-json-stable-stringify": "^2.0.0", 203 | "json-schema-traverse": "^0.4.1", 204 | "uri-js": "^4.2.2" 205 | }, 206 | "funding": { 207 | "type": "github", 208 | "url": "https://github.com/sponsors/epoberezkin" 209 | } 210 | }, 211 | "node_modules/ansi-regex": { 212 | "version": "5.0.1", 213 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 214 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 215 | "dev": true, 216 | "license": "MIT", 217 | "engines": { 218 | "node": ">=8" 219 | } 220 | }, 221 | "node_modules/ansi-styles": { 222 | "version": "4.3.0", 223 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 224 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 225 | "dev": true, 226 | "license": "MIT", 227 | "dependencies": { 228 | "color-convert": "^2.0.1" 229 | }, 230 | "engines": { 231 | "node": ">=8" 232 | }, 233 | "funding": { 234 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 235 | } 236 | }, 237 | "node_modules/argparse": { 238 | "version": "2.0.1", 239 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 240 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 241 | "dev": true, 242 | "license": "Python-2.0" 243 | }, 244 | "node_modules/array-buffer-byte-length": { 245 | "version": "1.0.1", 246 | "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", 247 | "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", 248 | "dev": true, 249 | "license": "MIT", 250 | "dependencies": { 251 | "call-bind": "^1.0.5", 252 | "is-array-buffer": "^3.0.4" 253 | }, 254 | "engines": { 255 | "node": ">= 0.4" 256 | }, 257 | "funding": { 258 | "url": "https://github.com/sponsors/ljharb" 259 | } 260 | }, 261 | "node_modules/array-includes": { 262 | "version": "3.1.8", 263 | "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", 264 | "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", 265 | "dev": true, 266 | "license": "MIT", 267 | "dependencies": { 268 | "call-bind": "^1.0.7", 269 | "define-properties": "^1.2.1", 270 | "es-abstract": "^1.23.2", 271 | "es-object-atoms": "^1.0.0", 272 | "get-intrinsic": "^1.2.4", 273 | "is-string": "^1.0.7" 274 | }, 275 | "engines": { 276 | "node": ">= 0.4" 277 | }, 278 | "funding": { 279 | "url": "https://github.com/sponsors/ljharb" 280 | } 281 | }, 282 | "node_modules/array.prototype.findlastindex": { 283 | "version": "1.2.5", 284 | "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", 285 | "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", 286 | "dev": true, 287 | "license": "MIT", 288 | "dependencies": { 289 | "call-bind": "^1.0.7", 290 | "define-properties": "^1.2.1", 291 | "es-abstract": "^1.23.2", 292 | "es-errors": "^1.3.0", 293 | "es-object-atoms": "^1.0.0", 294 | "es-shim-unscopables": "^1.0.2" 295 | }, 296 | "engines": { 297 | "node": ">= 0.4" 298 | }, 299 | "funding": { 300 | "url": "https://github.com/sponsors/ljharb" 301 | } 302 | }, 303 | "node_modules/array.prototype.flat": { 304 | "version": "1.3.2", 305 | "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", 306 | "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", 307 | "dev": true, 308 | "license": "MIT", 309 | "dependencies": { 310 | "call-bind": "^1.0.2", 311 | "define-properties": "^1.2.0", 312 | "es-abstract": "^1.22.1", 313 | "es-shim-unscopables": "^1.0.0" 314 | }, 315 | "engines": { 316 | "node": ">= 0.4" 317 | }, 318 | "funding": { 319 | "url": "https://github.com/sponsors/ljharb" 320 | } 321 | }, 322 | "node_modules/array.prototype.flatmap": { 323 | "version": "1.3.2", 324 | "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", 325 | "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", 326 | "dev": true, 327 | "license": "MIT", 328 | "dependencies": { 329 | "call-bind": "^1.0.2", 330 | "define-properties": "^1.2.0", 331 | "es-abstract": "^1.22.1", 332 | "es-shim-unscopables": "^1.0.0" 333 | }, 334 | "engines": { 335 | "node": ">= 0.4" 336 | }, 337 | "funding": { 338 | "url": "https://github.com/sponsors/ljharb" 339 | } 340 | }, 341 | "node_modules/arraybuffer.prototype.slice": { 342 | "version": "1.0.3", 343 | "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", 344 | "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", 345 | "dev": true, 346 | "license": "MIT", 347 | "dependencies": { 348 | "array-buffer-byte-length": "^1.0.1", 349 | "call-bind": "^1.0.5", 350 | "define-properties": "^1.2.1", 351 | "es-abstract": "^1.22.3", 352 | "es-errors": "^1.2.1", 353 | "get-intrinsic": "^1.2.3", 354 | "is-array-buffer": "^3.0.4", 355 | "is-shared-array-buffer": "^1.0.2" 356 | }, 357 | "engines": { 358 | "node": ">= 0.4" 359 | }, 360 | "funding": { 361 | "url": "https://github.com/sponsors/ljharb" 362 | } 363 | }, 364 | "node_modules/available-typed-arrays": { 365 | "version": "1.0.7", 366 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", 367 | "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", 368 | "dev": true, 369 | "license": "MIT", 370 | "dependencies": { 371 | "possible-typed-array-names": "^1.0.0" 372 | }, 373 | "engines": { 374 | "node": ">= 0.4" 375 | }, 376 | "funding": { 377 | "url": "https://github.com/sponsors/ljharb" 378 | } 379 | }, 380 | "node_modules/balanced-match": { 381 | "version": "1.0.2", 382 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 383 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 384 | "dev": true, 385 | "license": "MIT" 386 | }, 387 | "node_modules/brace-expansion": { 388 | "version": "1.1.11", 389 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 390 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 391 | "dev": true, 392 | "license": "MIT", 393 | "dependencies": { 394 | "balanced-match": "^1.0.0", 395 | "concat-map": "0.0.1" 396 | } 397 | }, 398 | "node_modules/call-bind": { 399 | "version": "1.0.7", 400 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 401 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 402 | "dev": true, 403 | "license": "MIT", 404 | "dependencies": { 405 | "es-define-property": "^1.0.0", 406 | "es-errors": "^1.3.0", 407 | "function-bind": "^1.1.2", 408 | "get-intrinsic": "^1.2.4", 409 | "set-function-length": "^1.2.1" 410 | }, 411 | "engines": { 412 | "node": ">= 0.4" 413 | }, 414 | "funding": { 415 | "url": "https://github.com/sponsors/ljharb" 416 | } 417 | }, 418 | "node_modules/callsites": { 419 | "version": "3.1.0", 420 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 421 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 422 | "dev": true, 423 | "license": "MIT", 424 | "engines": { 425 | "node": ">=6" 426 | } 427 | }, 428 | "node_modules/chalk": { 429 | "version": "4.1.2", 430 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 431 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 432 | "dev": true, 433 | "license": "MIT", 434 | "dependencies": { 435 | "ansi-styles": "^4.1.0", 436 | "supports-color": "^7.1.0" 437 | }, 438 | "engines": { 439 | "node": ">=10" 440 | }, 441 | "funding": { 442 | "url": "https://github.com/chalk/chalk?sponsor=1" 443 | } 444 | }, 445 | "node_modules/color-convert": { 446 | "version": "2.0.1", 447 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 448 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 449 | "dev": true, 450 | "license": "MIT", 451 | "dependencies": { 452 | "color-name": "~1.1.4" 453 | }, 454 | "engines": { 455 | "node": ">=7.0.0" 456 | } 457 | }, 458 | "node_modules/color-name": { 459 | "version": "1.1.4", 460 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 461 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 462 | "dev": true, 463 | "license": "MIT" 464 | }, 465 | "node_modules/commander": { 466 | "version": "6.2.1", 467 | "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", 468 | "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", 469 | "license": "MIT", 470 | "engines": { 471 | "node": ">= 6" 472 | } 473 | }, 474 | "node_modules/concat-map": { 475 | "version": "0.0.1", 476 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 477 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 478 | "dev": true, 479 | "license": "MIT" 480 | }, 481 | "node_modules/confusing-browser-globals": { 482 | "version": "1.0.11", 483 | "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", 484 | "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", 485 | "dev": true, 486 | "license": "MIT" 487 | }, 488 | "node_modules/cross-spawn": { 489 | "version": "7.0.3", 490 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 491 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 492 | "dev": true, 493 | "license": "MIT", 494 | "dependencies": { 495 | "path-key": "^3.1.0", 496 | "shebang-command": "^2.0.0", 497 | "which": "^2.0.1" 498 | }, 499 | "engines": { 500 | "node": ">= 8" 501 | } 502 | }, 503 | "node_modules/data-view-buffer": { 504 | "version": "1.0.1", 505 | "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", 506 | "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", 507 | "dev": true, 508 | "license": "MIT", 509 | "dependencies": { 510 | "call-bind": "^1.0.6", 511 | "es-errors": "^1.3.0", 512 | "is-data-view": "^1.0.1" 513 | }, 514 | "engines": { 515 | "node": ">= 0.4" 516 | }, 517 | "funding": { 518 | "url": "https://github.com/sponsors/ljharb" 519 | } 520 | }, 521 | "node_modules/data-view-byte-length": { 522 | "version": "1.0.1", 523 | "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", 524 | "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", 525 | "dev": true, 526 | "license": "MIT", 527 | "dependencies": { 528 | "call-bind": "^1.0.7", 529 | "es-errors": "^1.3.0", 530 | "is-data-view": "^1.0.1" 531 | }, 532 | "engines": { 533 | "node": ">= 0.4" 534 | }, 535 | "funding": { 536 | "url": "https://github.com/sponsors/ljharb" 537 | } 538 | }, 539 | "node_modules/data-view-byte-offset": { 540 | "version": "1.0.0", 541 | "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", 542 | "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", 543 | "dev": true, 544 | "license": "MIT", 545 | "dependencies": { 546 | "call-bind": "^1.0.6", 547 | "es-errors": "^1.3.0", 548 | "is-data-view": "^1.0.1" 549 | }, 550 | "engines": { 551 | "node": ">= 0.4" 552 | }, 553 | "funding": { 554 | "url": "https://github.com/sponsors/ljharb" 555 | } 556 | }, 557 | "node_modules/debug": { 558 | "version": "4.3.4", 559 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 560 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 561 | "dev": true, 562 | "license": "MIT", 563 | "dependencies": { 564 | "ms": "2.1.2" 565 | }, 566 | "engines": { 567 | "node": ">=6.0" 568 | }, 569 | "peerDependenciesMeta": { 570 | "supports-color": { 571 | "optional": true 572 | } 573 | } 574 | }, 575 | "node_modules/deep-is": { 576 | "version": "0.1.4", 577 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 578 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 579 | "dev": true, 580 | "license": "MIT" 581 | }, 582 | "node_modules/define-data-property": { 583 | "version": "1.1.4", 584 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 585 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 586 | "dev": true, 587 | "license": "MIT", 588 | "dependencies": { 589 | "es-define-property": "^1.0.0", 590 | "es-errors": "^1.3.0", 591 | "gopd": "^1.0.1" 592 | }, 593 | "engines": { 594 | "node": ">= 0.4" 595 | }, 596 | "funding": { 597 | "url": "https://github.com/sponsors/ljharb" 598 | } 599 | }, 600 | "node_modules/define-properties": { 601 | "version": "1.2.1", 602 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", 603 | "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", 604 | "dev": true, 605 | "license": "MIT", 606 | "dependencies": { 607 | "define-data-property": "^1.0.1", 608 | "has-property-descriptors": "^1.0.0", 609 | "object-keys": "^1.1.1" 610 | }, 611 | "engines": { 612 | "node": ">= 0.4" 613 | }, 614 | "funding": { 615 | "url": "https://github.com/sponsors/ljharb" 616 | } 617 | }, 618 | "node_modules/doctrine": { 619 | "version": "3.0.0", 620 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 621 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 622 | "dev": true, 623 | "license": "Apache-2.0", 624 | "dependencies": { 625 | "esutils": "^2.0.2" 626 | }, 627 | "engines": { 628 | "node": ">=6.0.0" 629 | } 630 | }, 631 | "node_modules/es-abstract": { 632 | "version": "1.23.3", 633 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", 634 | "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", 635 | "dev": true, 636 | "license": "MIT", 637 | "dependencies": { 638 | "array-buffer-byte-length": "^1.0.1", 639 | "arraybuffer.prototype.slice": "^1.0.3", 640 | "available-typed-arrays": "^1.0.7", 641 | "call-bind": "^1.0.7", 642 | "data-view-buffer": "^1.0.1", 643 | "data-view-byte-length": "^1.0.1", 644 | "data-view-byte-offset": "^1.0.0", 645 | "es-define-property": "^1.0.0", 646 | "es-errors": "^1.3.0", 647 | "es-object-atoms": "^1.0.0", 648 | "es-set-tostringtag": "^2.0.3", 649 | "es-to-primitive": "^1.2.1", 650 | "function.prototype.name": "^1.1.6", 651 | "get-intrinsic": "^1.2.4", 652 | "get-symbol-description": "^1.0.2", 653 | "globalthis": "^1.0.3", 654 | "gopd": "^1.0.1", 655 | "has-property-descriptors": "^1.0.2", 656 | "has-proto": "^1.0.3", 657 | "has-symbols": "^1.0.3", 658 | "hasown": "^2.0.2", 659 | "internal-slot": "^1.0.7", 660 | "is-array-buffer": "^3.0.4", 661 | "is-callable": "^1.2.7", 662 | "is-data-view": "^1.0.1", 663 | "is-negative-zero": "^2.0.3", 664 | "is-regex": "^1.1.4", 665 | "is-shared-array-buffer": "^1.0.3", 666 | "is-string": "^1.0.7", 667 | "is-typed-array": "^1.1.13", 668 | "is-weakref": "^1.0.2", 669 | "object-inspect": "^1.13.1", 670 | "object-keys": "^1.1.1", 671 | "object.assign": "^4.1.5", 672 | "regexp.prototype.flags": "^1.5.2", 673 | "safe-array-concat": "^1.1.2", 674 | "safe-regex-test": "^1.0.3", 675 | "string.prototype.trim": "^1.2.9", 676 | "string.prototype.trimend": "^1.0.8", 677 | "string.prototype.trimstart": "^1.0.8", 678 | "typed-array-buffer": "^1.0.2", 679 | "typed-array-byte-length": "^1.0.1", 680 | "typed-array-byte-offset": "^1.0.2", 681 | "typed-array-length": "^1.0.6", 682 | "unbox-primitive": "^1.0.2", 683 | "which-typed-array": "^1.1.15" 684 | }, 685 | "engines": { 686 | "node": ">= 0.4" 687 | }, 688 | "funding": { 689 | "url": "https://github.com/sponsors/ljharb" 690 | } 691 | }, 692 | "node_modules/es-define-property": { 693 | "version": "1.0.0", 694 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 695 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 696 | "dev": true, 697 | "license": "MIT", 698 | "dependencies": { 699 | "get-intrinsic": "^1.2.4" 700 | }, 701 | "engines": { 702 | "node": ">= 0.4" 703 | } 704 | }, 705 | "node_modules/es-errors": { 706 | "version": "1.3.0", 707 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 708 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 709 | "dev": true, 710 | "license": "MIT", 711 | "engines": { 712 | "node": ">= 0.4" 713 | } 714 | }, 715 | "node_modules/es-object-atoms": { 716 | "version": "1.0.0", 717 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", 718 | "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", 719 | "dev": true, 720 | "license": "MIT", 721 | "dependencies": { 722 | "es-errors": "^1.3.0" 723 | }, 724 | "engines": { 725 | "node": ">= 0.4" 726 | } 727 | }, 728 | "node_modules/es-set-tostringtag": { 729 | "version": "2.0.3", 730 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", 731 | "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", 732 | "dev": true, 733 | "license": "MIT", 734 | "dependencies": { 735 | "get-intrinsic": "^1.2.4", 736 | "has-tostringtag": "^1.0.2", 737 | "hasown": "^2.0.1" 738 | }, 739 | "engines": { 740 | "node": ">= 0.4" 741 | } 742 | }, 743 | "node_modules/es-shim-unscopables": { 744 | "version": "1.0.2", 745 | "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", 746 | "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", 747 | "dev": true, 748 | "license": "MIT", 749 | "dependencies": { 750 | "hasown": "^2.0.0" 751 | } 752 | }, 753 | "node_modules/es-to-primitive": { 754 | "version": "1.2.1", 755 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 756 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 757 | "dev": true, 758 | "license": "MIT", 759 | "dependencies": { 760 | "is-callable": "^1.1.4", 761 | "is-date-object": "^1.0.1", 762 | "is-symbol": "^1.0.2" 763 | }, 764 | "engines": { 765 | "node": ">= 0.4" 766 | }, 767 | "funding": { 768 | "url": "https://github.com/sponsors/ljharb" 769 | } 770 | }, 771 | "node_modules/escape-string-regexp": { 772 | "version": "4.0.0", 773 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 774 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 775 | "dev": true, 776 | "license": "MIT", 777 | "engines": { 778 | "node": ">=10" 779 | }, 780 | "funding": { 781 | "url": "https://github.com/sponsors/sindresorhus" 782 | } 783 | }, 784 | "node_modules/eslint": { 785 | "version": "8.57.0", 786 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", 787 | "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", 788 | "dev": true, 789 | "license": "MIT", 790 | "dependencies": { 791 | "@eslint-community/eslint-utils": "^4.2.0", 792 | "@eslint-community/regexpp": "^4.6.1", 793 | "@eslint/eslintrc": "^2.1.4", 794 | "@eslint/js": "8.57.0", 795 | "@humanwhocodes/config-array": "^0.11.14", 796 | "@humanwhocodes/module-importer": "^1.0.1", 797 | "@nodelib/fs.walk": "^1.2.8", 798 | "@ungap/structured-clone": "^1.2.0", 799 | "ajv": "^6.12.4", 800 | "chalk": "^4.0.0", 801 | "cross-spawn": "^7.0.2", 802 | "debug": "^4.3.2", 803 | "doctrine": "^3.0.0", 804 | "escape-string-regexp": "^4.0.0", 805 | "eslint-scope": "^7.2.2", 806 | "eslint-visitor-keys": "^3.4.3", 807 | "espree": "^9.6.1", 808 | "esquery": "^1.4.2", 809 | "esutils": "^2.0.2", 810 | "fast-deep-equal": "^3.1.3", 811 | "file-entry-cache": "^6.0.1", 812 | "find-up": "^5.0.0", 813 | "glob-parent": "^6.0.2", 814 | "globals": "^13.19.0", 815 | "graphemer": "^1.4.0", 816 | "ignore": "^5.2.0", 817 | "imurmurhash": "^0.1.4", 818 | "is-glob": "^4.0.0", 819 | "is-path-inside": "^3.0.3", 820 | "js-yaml": "^4.1.0", 821 | "json-stable-stringify-without-jsonify": "^1.0.1", 822 | "levn": "^0.4.1", 823 | "lodash.merge": "^4.6.2", 824 | "minimatch": "^3.1.2", 825 | "natural-compare": "^1.4.0", 826 | "optionator": "^0.9.3", 827 | "strip-ansi": "^6.0.1", 828 | "text-table": "^0.2.0" 829 | }, 830 | "bin": { 831 | "eslint": "bin/eslint.js" 832 | }, 833 | "engines": { 834 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 835 | }, 836 | "funding": { 837 | "url": "https://opencollective.com/eslint" 838 | } 839 | }, 840 | "node_modules/eslint-config-airbnb-base": { 841 | "version": "15.0.0", 842 | "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", 843 | "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", 844 | "dev": true, 845 | "license": "MIT", 846 | "dependencies": { 847 | "confusing-browser-globals": "^1.0.10", 848 | "object.assign": "^4.1.2", 849 | "object.entries": "^1.1.5", 850 | "semver": "^6.3.0" 851 | }, 852 | "engines": { 853 | "node": "^10.12.0 || >=12.0.0" 854 | }, 855 | "peerDependencies": { 856 | "eslint": "^7.32.0 || ^8.2.0", 857 | "eslint-plugin-import": "^2.25.2" 858 | } 859 | }, 860 | "node_modules/eslint-import-resolver-node": { 861 | "version": "0.3.9", 862 | "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", 863 | "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", 864 | "dev": true, 865 | "license": "MIT", 866 | "dependencies": { 867 | "debug": "^3.2.7", 868 | "is-core-module": "^2.13.0", 869 | "resolve": "^1.22.4" 870 | } 871 | }, 872 | "node_modules/eslint-import-resolver-node/node_modules/debug": { 873 | "version": "3.2.7", 874 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 875 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 876 | "dev": true, 877 | "license": "MIT", 878 | "dependencies": { 879 | "ms": "^2.1.1" 880 | } 881 | }, 882 | "node_modules/eslint-module-utils": { 883 | "version": "2.8.1", 884 | "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", 885 | "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", 886 | "dev": true, 887 | "license": "MIT", 888 | "dependencies": { 889 | "debug": "^3.2.7" 890 | }, 891 | "engines": { 892 | "node": ">=4" 893 | }, 894 | "peerDependenciesMeta": { 895 | "eslint": { 896 | "optional": true 897 | } 898 | } 899 | }, 900 | "node_modules/eslint-module-utils/node_modules/debug": { 901 | "version": "3.2.7", 902 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 903 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 904 | "dev": true, 905 | "license": "MIT", 906 | "dependencies": { 907 | "ms": "^2.1.1" 908 | } 909 | }, 910 | "node_modules/eslint-plugin-import": { 911 | "version": "2.29.1", 912 | "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", 913 | "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", 914 | "dev": true, 915 | "license": "MIT", 916 | "dependencies": { 917 | "array-includes": "^3.1.7", 918 | "array.prototype.findlastindex": "^1.2.3", 919 | "array.prototype.flat": "^1.3.2", 920 | "array.prototype.flatmap": "^1.3.2", 921 | "debug": "^3.2.7", 922 | "doctrine": "^2.1.0", 923 | "eslint-import-resolver-node": "^0.3.9", 924 | "eslint-module-utils": "^2.8.0", 925 | "hasown": "^2.0.0", 926 | "is-core-module": "^2.13.1", 927 | "is-glob": "^4.0.3", 928 | "minimatch": "^3.1.2", 929 | "object.fromentries": "^2.0.7", 930 | "object.groupby": "^1.0.1", 931 | "object.values": "^1.1.7", 932 | "semver": "^6.3.1", 933 | "tsconfig-paths": "^3.15.0" 934 | }, 935 | "engines": { 936 | "node": ">=4" 937 | }, 938 | "peerDependencies": { 939 | "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" 940 | } 941 | }, 942 | "node_modules/eslint-plugin-import/node_modules/debug": { 943 | "version": "3.2.7", 944 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 945 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 946 | "dev": true, 947 | "license": "MIT", 948 | "dependencies": { 949 | "ms": "^2.1.1" 950 | } 951 | }, 952 | "node_modules/eslint-plugin-import/node_modules/doctrine": { 953 | "version": "2.1.0", 954 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", 955 | "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", 956 | "dev": true, 957 | "license": "Apache-2.0", 958 | "dependencies": { 959 | "esutils": "^2.0.2" 960 | }, 961 | "engines": { 962 | "node": ">=0.10.0" 963 | } 964 | }, 965 | "node_modules/eslint-scope": { 966 | "version": "7.2.2", 967 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", 968 | "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", 969 | "dev": true, 970 | "license": "BSD-2-Clause", 971 | "dependencies": { 972 | "esrecurse": "^4.3.0", 973 | "estraverse": "^5.2.0" 974 | }, 975 | "engines": { 976 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 977 | }, 978 | "funding": { 979 | "url": "https://opencollective.com/eslint" 980 | } 981 | }, 982 | "node_modules/eslint-visitor-keys": { 983 | "version": "3.4.3", 984 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 985 | "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 986 | "dev": true, 987 | "license": "Apache-2.0", 988 | "engines": { 989 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 990 | }, 991 | "funding": { 992 | "url": "https://opencollective.com/eslint" 993 | } 994 | }, 995 | "node_modules/espree": { 996 | "version": "9.6.1", 997 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", 998 | "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", 999 | "dev": true, 1000 | "license": "BSD-2-Clause", 1001 | "dependencies": { 1002 | "acorn": "^8.9.0", 1003 | "acorn-jsx": "^5.3.2", 1004 | "eslint-visitor-keys": "^3.4.1" 1005 | }, 1006 | "engines": { 1007 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1008 | }, 1009 | "funding": { 1010 | "url": "https://opencollective.com/eslint" 1011 | } 1012 | }, 1013 | "node_modules/esquery": { 1014 | "version": "1.5.0", 1015 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", 1016 | "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", 1017 | "dev": true, 1018 | "license": "BSD-3-Clause", 1019 | "dependencies": { 1020 | "estraverse": "^5.1.0" 1021 | }, 1022 | "engines": { 1023 | "node": ">=0.10" 1024 | } 1025 | }, 1026 | "node_modules/esrecurse": { 1027 | "version": "4.3.0", 1028 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 1029 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 1030 | "dev": true, 1031 | "license": "BSD-2-Clause", 1032 | "dependencies": { 1033 | "estraverse": "^5.2.0" 1034 | }, 1035 | "engines": { 1036 | "node": ">=4.0" 1037 | } 1038 | }, 1039 | "node_modules/estraverse": { 1040 | "version": "5.3.0", 1041 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1042 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1043 | "dev": true, 1044 | "license": "BSD-2-Clause", 1045 | "engines": { 1046 | "node": ">=4.0" 1047 | } 1048 | }, 1049 | "node_modules/esutils": { 1050 | "version": "2.0.3", 1051 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1052 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1053 | "dev": true, 1054 | "license": "BSD-2-Clause", 1055 | "engines": { 1056 | "node": ">=0.10.0" 1057 | } 1058 | }, 1059 | "node_modules/fast-deep-equal": { 1060 | "version": "3.1.3", 1061 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1062 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1063 | "dev": true, 1064 | "license": "MIT" 1065 | }, 1066 | "node_modules/fast-json-stable-stringify": { 1067 | "version": "2.1.0", 1068 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1069 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 1070 | "dev": true, 1071 | "license": "MIT" 1072 | }, 1073 | "node_modules/fast-levenshtein": { 1074 | "version": "2.0.6", 1075 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 1076 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 1077 | "dev": true, 1078 | "license": "MIT" 1079 | }, 1080 | "node_modules/fastq": { 1081 | "version": "1.17.1", 1082 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 1083 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 1084 | "dev": true, 1085 | "license": "ISC", 1086 | "dependencies": { 1087 | "reusify": "^1.0.4" 1088 | } 1089 | }, 1090 | "node_modules/file-entry-cache": { 1091 | "version": "6.0.1", 1092 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 1093 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 1094 | "dev": true, 1095 | "license": "MIT", 1096 | "dependencies": { 1097 | "flat-cache": "^3.0.4" 1098 | }, 1099 | "engines": { 1100 | "node": "^10.12.0 || >=12.0.0" 1101 | } 1102 | }, 1103 | "node_modules/find-up": { 1104 | "version": "5.0.0", 1105 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1106 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1107 | "dev": true, 1108 | "license": "MIT", 1109 | "dependencies": { 1110 | "locate-path": "^6.0.0", 1111 | "path-exists": "^4.0.0" 1112 | }, 1113 | "engines": { 1114 | "node": ">=10" 1115 | }, 1116 | "funding": { 1117 | "url": "https://github.com/sponsors/sindresorhus" 1118 | } 1119 | }, 1120 | "node_modules/flat-cache": { 1121 | "version": "3.2.0", 1122 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", 1123 | "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", 1124 | "dev": true, 1125 | "license": "MIT", 1126 | "dependencies": { 1127 | "flatted": "^3.2.9", 1128 | "keyv": "^4.5.3", 1129 | "rimraf": "^3.0.2" 1130 | }, 1131 | "engines": { 1132 | "node": "^10.12.0 || >=12.0.0" 1133 | } 1134 | }, 1135 | "node_modules/flatted": { 1136 | "version": "3.3.1", 1137 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", 1138 | "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", 1139 | "dev": true, 1140 | "license": "ISC" 1141 | }, 1142 | "node_modules/for-each": { 1143 | "version": "0.3.3", 1144 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 1145 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 1146 | "dev": true, 1147 | "license": "MIT", 1148 | "dependencies": { 1149 | "is-callable": "^1.1.3" 1150 | } 1151 | }, 1152 | "node_modules/fs.realpath": { 1153 | "version": "1.0.0", 1154 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1155 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1156 | "dev": true, 1157 | "license": "ISC" 1158 | }, 1159 | "node_modules/function-bind": { 1160 | "version": "1.1.2", 1161 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1162 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1163 | "dev": true, 1164 | "license": "MIT", 1165 | "funding": { 1166 | "url": "https://github.com/sponsors/ljharb" 1167 | } 1168 | }, 1169 | "node_modules/function.prototype.name": { 1170 | "version": "1.1.6", 1171 | "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", 1172 | "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", 1173 | "dev": true, 1174 | "license": "MIT", 1175 | "dependencies": { 1176 | "call-bind": "^1.0.2", 1177 | "define-properties": "^1.2.0", 1178 | "es-abstract": "^1.22.1", 1179 | "functions-have-names": "^1.2.3" 1180 | }, 1181 | "engines": { 1182 | "node": ">= 0.4" 1183 | }, 1184 | "funding": { 1185 | "url": "https://github.com/sponsors/ljharb" 1186 | } 1187 | }, 1188 | "node_modules/functions-have-names": { 1189 | "version": "1.2.3", 1190 | "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", 1191 | "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", 1192 | "dev": true, 1193 | "license": "MIT", 1194 | "funding": { 1195 | "url": "https://github.com/sponsors/ljharb" 1196 | } 1197 | }, 1198 | "node_modules/get-intrinsic": { 1199 | "version": "1.2.4", 1200 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 1201 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 1202 | "dev": true, 1203 | "license": "MIT", 1204 | "dependencies": { 1205 | "es-errors": "^1.3.0", 1206 | "function-bind": "^1.1.2", 1207 | "has-proto": "^1.0.1", 1208 | "has-symbols": "^1.0.3", 1209 | "hasown": "^2.0.0" 1210 | }, 1211 | "engines": { 1212 | "node": ">= 0.4" 1213 | }, 1214 | "funding": { 1215 | "url": "https://github.com/sponsors/ljharb" 1216 | } 1217 | }, 1218 | "node_modules/get-symbol-description": { 1219 | "version": "1.0.2", 1220 | "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", 1221 | "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", 1222 | "dev": true, 1223 | "license": "MIT", 1224 | "dependencies": { 1225 | "call-bind": "^1.0.5", 1226 | "es-errors": "^1.3.0", 1227 | "get-intrinsic": "^1.2.4" 1228 | }, 1229 | "engines": { 1230 | "node": ">= 0.4" 1231 | }, 1232 | "funding": { 1233 | "url": "https://github.com/sponsors/ljharb" 1234 | } 1235 | }, 1236 | "node_modules/glob": { 1237 | "version": "7.2.3", 1238 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1239 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1240 | "deprecated": "Glob versions prior to v9 are no longer supported", 1241 | "dev": true, 1242 | "license": "ISC", 1243 | "dependencies": { 1244 | "fs.realpath": "^1.0.0", 1245 | "inflight": "^1.0.4", 1246 | "inherits": "2", 1247 | "minimatch": "^3.1.1", 1248 | "once": "^1.3.0", 1249 | "path-is-absolute": "^1.0.0" 1250 | }, 1251 | "engines": { 1252 | "node": "*" 1253 | }, 1254 | "funding": { 1255 | "url": "https://github.com/sponsors/isaacs" 1256 | } 1257 | }, 1258 | "node_modules/glob-parent": { 1259 | "version": "6.0.2", 1260 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1261 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1262 | "dev": true, 1263 | "license": "ISC", 1264 | "dependencies": { 1265 | "is-glob": "^4.0.3" 1266 | }, 1267 | "engines": { 1268 | "node": ">=10.13.0" 1269 | } 1270 | }, 1271 | "node_modules/globals": { 1272 | "version": "13.24.0", 1273 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", 1274 | "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", 1275 | "dev": true, 1276 | "license": "MIT", 1277 | "dependencies": { 1278 | "type-fest": "^0.20.2" 1279 | }, 1280 | "engines": { 1281 | "node": ">=8" 1282 | }, 1283 | "funding": { 1284 | "url": "https://github.com/sponsors/sindresorhus" 1285 | } 1286 | }, 1287 | "node_modules/globalthis": { 1288 | "version": "1.0.4", 1289 | "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", 1290 | "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", 1291 | "dev": true, 1292 | "license": "MIT", 1293 | "dependencies": { 1294 | "define-properties": "^1.2.1", 1295 | "gopd": "^1.0.1" 1296 | }, 1297 | "engines": { 1298 | "node": ">= 0.4" 1299 | }, 1300 | "funding": { 1301 | "url": "https://github.com/sponsors/ljharb" 1302 | } 1303 | }, 1304 | "node_modules/gopd": { 1305 | "version": "1.0.1", 1306 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 1307 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 1308 | "dev": true, 1309 | "license": "MIT", 1310 | "dependencies": { 1311 | "get-intrinsic": "^1.1.3" 1312 | }, 1313 | "funding": { 1314 | "url": "https://github.com/sponsors/ljharb" 1315 | } 1316 | }, 1317 | "node_modules/graphemer": { 1318 | "version": "1.4.0", 1319 | "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 1320 | "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 1321 | "dev": true, 1322 | "license": "MIT" 1323 | }, 1324 | "node_modules/has-bigints": { 1325 | "version": "1.0.2", 1326 | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", 1327 | "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", 1328 | "dev": true, 1329 | "license": "MIT", 1330 | "funding": { 1331 | "url": "https://github.com/sponsors/ljharb" 1332 | } 1333 | }, 1334 | "node_modules/has-flag": { 1335 | "version": "4.0.0", 1336 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1337 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1338 | "dev": true, 1339 | "license": "MIT", 1340 | "engines": { 1341 | "node": ">=8" 1342 | } 1343 | }, 1344 | "node_modules/has-property-descriptors": { 1345 | "version": "1.0.2", 1346 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 1347 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 1348 | "dev": true, 1349 | "license": "MIT", 1350 | "dependencies": { 1351 | "es-define-property": "^1.0.0" 1352 | }, 1353 | "funding": { 1354 | "url": "https://github.com/sponsors/ljharb" 1355 | } 1356 | }, 1357 | "node_modules/has-proto": { 1358 | "version": "1.0.3", 1359 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 1360 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 1361 | "dev": true, 1362 | "license": "MIT", 1363 | "engines": { 1364 | "node": ">= 0.4" 1365 | }, 1366 | "funding": { 1367 | "url": "https://github.com/sponsors/ljharb" 1368 | } 1369 | }, 1370 | "node_modules/has-symbols": { 1371 | "version": "1.0.3", 1372 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 1373 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 1374 | "dev": true, 1375 | "license": "MIT", 1376 | "engines": { 1377 | "node": ">= 0.4" 1378 | }, 1379 | "funding": { 1380 | "url": "https://github.com/sponsors/ljharb" 1381 | } 1382 | }, 1383 | "node_modules/has-tostringtag": { 1384 | "version": "1.0.2", 1385 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 1386 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 1387 | "dev": true, 1388 | "license": "MIT", 1389 | "dependencies": { 1390 | "has-symbols": "^1.0.3" 1391 | }, 1392 | "engines": { 1393 | "node": ">= 0.4" 1394 | }, 1395 | "funding": { 1396 | "url": "https://github.com/sponsors/ljharb" 1397 | } 1398 | }, 1399 | "node_modules/hasown": { 1400 | "version": "2.0.2", 1401 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1402 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1403 | "dev": true, 1404 | "license": "MIT", 1405 | "dependencies": { 1406 | "function-bind": "^1.1.2" 1407 | }, 1408 | "engines": { 1409 | "node": ">= 0.4" 1410 | } 1411 | }, 1412 | "node_modules/ignore": { 1413 | "version": "5.3.1", 1414 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", 1415 | "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", 1416 | "dev": true, 1417 | "license": "MIT", 1418 | "engines": { 1419 | "node": ">= 4" 1420 | } 1421 | }, 1422 | "node_modules/import-fresh": { 1423 | "version": "3.3.0", 1424 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1425 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1426 | "dev": true, 1427 | "license": "MIT", 1428 | "dependencies": { 1429 | "parent-module": "^1.0.0", 1430 | "resolve-from": "^4.0.0" 1431 | }, 1432 | "engines": { 1433 | "node": ">=6" 1434 | }, 1435 | "funding": { 1436 | "url": "https://github.com/sponsors/sindresorhus" 1437 | } 1438 | }, 1439 | "node_modules/imurmurhash": { 1440 | "version": "0.1.4", 1441 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1442 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1443 | "dev": true, 1444 | "license": "MIT", 1445 | "engines": { 1446 | "node": ">=0.8.19" 1447 | } 1448 | }, 1449 | "node_modules/inflight": { 1450 | "version": "1.0.6", 1451 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1452 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1453 | "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 1454 | "dev": true, 1455 | "license": "ISC", 1456 | "dependencies": { 1457 | "once": "^1.3.0", 1458 | "wrappy": "1" 1459 | } 1460 | }, 1461 | "node_modules/inherits": { 1462 | "version": "2.0.4", 1463 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1464 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1465 | "dev": true, 1466 | "license": "ISC" 1467 | }, 1468 | "node_modules/internal-slot": { 1469 | "version": "1.0.7", 1470 | "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", 1471 | "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", 1472 | "dev": true, 1473 | "license": "MIT", 1474 | "dependencies": { 1475 | "es-errors": "^1.3.0", 1476 | "hasown": "^2.0.0", 1477 | "side-channel": "^1.0.4" 1478 | }, 1479 | "engines": { 1480 | "node": ">= 0.4" 1481 | } 1482 | }, 1483 | "node_modules/is-array-buffer": { 1484 | "version": "3.0.4", 1485 | "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", 1486 | "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", 1487 | "dev": true, 1488 | "license": "MIT", 1489 | "dependencies": { 1490 | "call-bind": "^1.0.2", 1491 | "get-intrinsic": "^1.2.1" 1492 | }, 1493 | "engines": { 1494 | "node": ">= 0.4" 1495 | }, 1496 | "funding": { 1497 | "url": "https://github.com/sponsors/ljharb" 1498 | } 1499 | }, 1500 | "node_modules/is-bigint": { 1501 | "version": "1.0.4", 1502 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", 1503 | "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", 1504 | "dev": true, 1505 | "license": "MIT", 1506 | "dependencies": { 1507 | "has-bigints": "^1.0.1" 1508 | }, 1509 | "funding": { 1510 | "url": "https://github.com/sponsors/ljharb" 1511 | } 1512 | }, 1513 | "node_modules/is-boolean-object": { 1514 | "version": "1.1.2", 1515 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", 1516 | "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", 1517 | "dev": true, 1518 | "license": "MIT", 1519 | "dependencies": { 1520 | "call-bind": "^1.0.2", 1521 | "has-tostringtag": "^1.0.0" 1522 | }, 1523 | "engines": { 1524 | "node": ">= 0.4" 1525 | }, 1526 | "funding": { 1527 | "url": "https://github.com/sponsors/ljharb" 1528 | } 1529 | }, 1530 | "node_modules/is-callable": { 1531 | "version": "1.2.7", 1532 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", 1533 | "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", 1534 | "dev": true, 1535 | "license": "MIT", 1536 | "engines": { 1537 | "node": ">= 0.4" 1538 | }, 1539 | "funding": { 1540 | "url": "https://github.com/sponsors/ljharb" 1541 | } 1542 | }, 1543 | "node_modules/is-core-module": { 1544 | "version": "2.13.1", 1545 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", 1546 | "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", 1547 | "dev": true, 1548 | "license": "MIT", 1549 | "dependencies": { 1550 | "hasown": "^2.0.0" 1551 | }, 1552 | "funding": { 1553 | "url": "https://github.com/sponsors/ljharb" 1554 | } 1555 | }, 1556 | "node_modules/is-data-view": { 1557 | "version": "1.0.1", 1558 | "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", 1559 | "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", 1560 | "dev": true, 1561 | "license": "MIT", 1562 | "dependencies": { 1563 | "is-typed-array": "^1.1.13" 1564 | }, 1565 | "engines": { 1566 | "node": ">= 0.4" 1567 | }, 1568 | "funding": { 1569 | "url": "https://github.com/sponsors/ljharb" 1570 | } 1571 | }, 1572 | "node_modules/is-date-object": { 1573 | "version": "1.0.5", 1574 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", 1575 | "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", 1576 | "dev": true, 1577 | "license": "MIT", 1578 | "dependencies": { 1579 | "has-tostringtag": "^1.0.0" 1580 | }, 1581 | "engines": { 1582 | "node": ">= 0.4" 1583 | }, 1584 | "funding": { 1585 | "url": "https://github.com/sponsors/ljharb" 1586 | } 1587 | }, 1588 | "node_modules/is-extglob": { 1589 | "version": "2.1.1", 1590 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1591 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1592 | "dev": true, 1593 | "license": "MIT", 1594 | "engines": { 1595 | "node": ">=0.10.0" 1596 | } 1597 | }, 1598 | "node_modules/is-glob": { 1599 | "version": "4.0.3", 1600 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1601 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1602 | "dev": true, 1603 | "license": "MIT", 1604 | "dependencies": { 1605 | "is-extglob": "^2.1.1" 1606 | }, 1607 | "engines": { 1608 | "node": ">=0.10.0" 1609 | } 1610 | }, 1611 | "node_modules/is-negative-zero": { 1612 | "version": "2.0.3", 1613 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", 1614 | "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", 1615 | "dev": true, 1616 | "license": "MIT", 1617 | "engines": { 1618 | "node": ">= 0.4" 1619 | }, 1620 | "funding": { 1621 | "url": "https://github.com/sponsors/ljharb" 1622 | } 1623 | }, 1624 | "node_modules/is-number-object": { 1625 | "version": "1.0.7", 1626 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", 1627 | "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", 1628 | "dev": true, 1629 | "license": "MIT", 1630 | "dependencies": { 1631 | "has-tostringtag": "^1.0.0" 1632 | }, 1633 | "engines": { 1634 | "node": ">= 0.4" 1635 | }, 1636 | "funding": { 1637 | "url": "https://github.com/sponsors/ljharb" 1638 | } 1639 | }, 1640 | "node_modules/is-path-inside": { 1641 | "version": "3.0.3", 1642 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", 1643 | "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", 1644 | "dev": true, 1645 | "license": "MIT", 1646 | "engines": { 1647 | "node": ">=8" 1648 | } 1649 | }, 1650 | "node_modules/is-regex": { 1651 | "version": "1.1.4", 1652 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", 1653 | "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", 1654 | "dev": true, 1655 | "license": "MIT", 1656 | "dependencies": { 1657 | "call-bind": "^1.0.2", 1658 | "has-tostringtag": "^1.0.0" 1659 | }, 1660 | "engines": { 1661 | "node": ">= 0.4" 1662 | }, 1663 | "funding": { 1664 | "url": "https://github.com/sponsors/ljharb" 1665 | } 1666 | }, 1667 | "node_modules/is-shared-array-buffer": { 1668 | "version": "1.0.3", 1669 | "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", 1670 | "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", 1671 | "dev": true, 1672 | "license": "MIT", 1673 | "dependencies": { 1674 | "call-bind": "^1.0.7" 1675 | }, 1676 | "engines": { 1677 | "node": ">= 0.4" 1678 | }, 1679 | "funding": { 1680 | "url": "https://github.com/sponsors/ljharb" 1681 | } 1682 | }, 1683 | "node_modules/is-string": { 1684 | "version": "1.0.7", 1685 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", 1686 | "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", 1687 | "dev": true, 1688 | "license": "MIT", 1689 | "dependencies": { 1690 | "has-tostringtag": "^1.0.0" 1691 | }, 1692 | "engines": { 1693 | "node": ">= 0.4" 1694 | }, 1695 | "funding": { 1696 | "url": "https://github.com/sponsors/ljharb" 1697 | } 1698 | }, 1699 | "node_modules/is-symbol": { 1700 | "version": "1.0.4", 1701 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", 1702 | "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", 1703 | "dev": true, 1704 | "license": "MIT", 1705 | "dependencies": { 1706 | "has-symbols": "^1.0.2" 1707 | }, 1708 | "engines": { 1709 | "node": ">= 0.4" 1710 | }, 1711 | "funding": { 1712 | "url": "https://github.com/sponsors/ljharb" 1713 | } 1714 | }, 1715 | "node_modules/is-typed-array": { 1716 | "version": "1.1.13", 1717 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", 1718 | "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", 1719 | "dev": true, 1720 | "license": "MIT", 1721 | "dependencies": { 1722 | "which-typed-array": "^1.1.14" 1723 | }, 1724 | "engines": { 1725 | "node": ">= 0.4" 1726 | }, 1727 | "funding": { 1728 | "url": "https://github.com/sponsors/ljharb" 1729 | } 1730 | }, 1731 | "node_modules/is-weakref": { 1732 | "version": "1.0.2", 1733 | "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", 1734 | "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", 1735 | "dev": true, 1736 | "license": "MIT", 1737 | "dependencies": { 1738 | "call-bind": "^1.0.2" 1739 | }, 1740 | "funding": { 1741 | "url": "https://github.com/sponsors/ljharb" 1742 | } 1743 | }, 1744 | "node_modules/isarray": { 1745 | "version": "2.0.5", 1746 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 1747 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 1748 | "dev": true, 1749 | "license": "MIT" 1750 | }, 1751 | "node_modules/isexe": { 1752 | "version": "2.0.0", 1753 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1754 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1755 | "dev": true, 1756 | "license": "ISC" 1757 | }, 1758 | "node_modules/js-yaml": { 1759 | "version": "4.1.0", 1760 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1761 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1762 | "dev": true, 1763 | "license": "MIT", 1764 | "dependencies": { 1765 | "argparse": "^2.0.1" 1766 | }, 1767 | "bin": { 1768 | "js-yaml": "bin/js-yaml.js" 1769 | } 1770 | }, 1771 | "node_modules/json-buffer": { 1772 | "version": "3.0.1", 1773 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 1774 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 1775 | "dev": true, 1776 | "license": "MIT" 1777 | }, 1778 | "node_modules/json-schema-traverse": { 1779 | "version": "0.4.1", 1780 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1781 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1782 | "dev": true, 1783 | "license": "MIT" 1784 | }, 1785 | "node_modules/json-stable-stringify-without-jsonify": { 1786 | "version": "1.0.1", 1787 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1788 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 1789 | "dev": true, 1790 | "license": "MIT" 1791 | }, 1792 | "node_modules/json5": { 1793 | "version": "1.0.2", 1794 | "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", 1795 | "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", 1796 | "dev": true, 1797 | "license": "MIT", 1798 | "dependencies": { 1799 | "minimist": "^1.2.0" 1800 | }, 1801 | "bin": { 1802 | "json5": "lib/cli.js" 1803 | } 1804 | }, 1805 | "node_modules/keyv": { 1806 | "version": "4.5.4", 1807 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 1808 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 1809 | "dev": true, 1810 | "license": "MIT", 1811 | "dependencies": { 1812 | "json-buffer": "3.0.1" 1813 | } 1814 | }, 1815 | "node_modules/levn": { 1816 | "version": "0.4.1", 1817 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1818 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1819 | "dev": true, 1820 | "license": "MIT", 1821 | "dependencies": { 1822 | "prelude-ls": "^1.2.1", 1823 | "type-check": "~0.4.0" 1824 | }, 1825 | "engines": { 1826 | "node": ">= 0.8.0" 1827 | } 1828 | }, 1829 | "node_modules/locate-path": { 1830 | "version": "6.0.0", 1831 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1832 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1833 | "dev": true, 1834 | "license": "MIT", 1835 | "dependencies": { 1836 | "p-locate": "^5.0.0" 1837 | }, 1838 | "engines": { 1839 | "node": ">=10" 1840 | }, 1841 | "funding": { 1842 | "url": "https://github.com/sponsors/sindresorhus" 1843 | } 1844 | }, 1845 | "node_modules/lodash.merge": { 1846 | "version": "4.6.2", 1847 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1848 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1849 | "dev": true, 1850 | "license": "MIT" 1851 | }, 1852 | "node_modules/minimatch": { 1853 | "version": "3.1.2", 1854 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1855 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1856 | "dev": true, 1857 | "license": "ISC", 1858 | "dependencies": { 1859 | "brace-expansion": "^1.1.7" 1860 | }, 1861 | "engines": { 1862 | "node": "*" 1863 | } 1864 | }, 1865 | "node_modules/minimist": { 1866 | "version": "1.2.8", 1867 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1868 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1869 | "dev": true, 1870 | "license": "MIT", 1871 | "funding": { 1872 | "url": "https://github.com/sponsors/ljharb" 1873 | } 1874 | }, 1875 | "node_modules/ms": { 1876 | "version": "2.1.2", 1877 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1878 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1879 | "dev": true, 1880 | "license": "MIT" 1881 | }, 1882 | "node_modules/natural-compare": { 1883 | "version": "1.4.0", 1884 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1885 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 1886 | "dev": true, 1887 | "license": "MIT" 1888 | }, 1889 | "node_modules/object-inspect": { 1890 | "version": "1.13.1", 1891 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 1892 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 1893 | "dev": true, 1894 | "license": "MIT", 1895 | "funding": { 1896 | "url": "https://github.com/sponsors/ljharb" 1897 | } 1898 | }, 1899 | "node_modules/object-keys": { 1900 | "version": "1.1.1", 1901 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 1902 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 1903 | "dev": true, 1904 | "license": "MIT", 1905 | "engines": { 1906 | "node": ">= 0.4" 1907 | } 1908 | }, 1909 | "node_modules/object.assign": { 1910 | "version": "4.1.5", 1911 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", 1912 | "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", 1913 | "dev": true, 1914 | "license": "MIT", 1915 | "dependencies": { 1916 | "call-bind": "^1.0.5", 1917 | "define-properties": "^1.2.1", 1918 | "has-symbols": "^1.0.3", 1919 | "object-keys": "^1.1.1" 1920 | }, 1921 | "engines": { 1922 | "node": ">= 0.4" 1923 | }, 1924 | "funding": { 1925 | "url": "https://github.com/sponsors/ljharb" 1926 | } 1927 | }, 1928 | "node_modules/object.entries": { 1929 | "version": "1.1.8", 1930 | "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", 1931 | "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", 1932 | "dev": true, 1933 | "license": "MIT", 1934 | "dependencies": { 1935 | "call-bind": "^1.0.7", 1936 | "define-properties": "^1.2.1", 1937 | "es-object-atoms": "^1.0.0" 1938 | }, 1939 | "engines": { 1940 | "node": ">= 0.4" 1941 | } 1942 | }, 1943 | "node_modules/object.fromentries": { 1944 | "version": "2.0.8", 1945 | "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", 1946 | "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", 1947 | "dev": true, 1948 | "license": "MIT", 1949 | "dependencies": { 1950 | "call-bind": "^1.0.7", 1951 | "define-properties": "^1.2.1", 1952 | "es-abstract": "^1.23.2", 1953 | "es-object-atoms": "^1.0.0" 1954 | }, 1955 | "engines": { 1956 | "node": ">= 0.4" 1957 | }, 1958 | "funding": { 1959 | "url": "https://github.com/sponsors/ljharb" 1960 | } 1961 | }, 1962 | "node_modules/object.groupby": { 1963 | "version": "1.0.3", 1964 | "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", 1965 | "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", 1966 | "dev": true, 1967 | "license": "MIT", 1968 | "dependencies": { 1969 | "call-bind": "^1.0.7", 1970 | "define-properties": "^1.2.1", 1971 | "es-abstract": "^1.23.2" 1972 | }, 1973 | "engines": { 1974 | "node": ">= 0.4" 1975 | } 1976 | }, 1977 | "node_modules/object.values": { 1978 | "version": "1.2.0", 1979 | "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", 1980 | "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", 1981 | "dev": true, 1982 | "license": "MIT", 1983 | "dependencies": { 1984 | "call-bind": "^1.0.7", 1985 | "define-properties": "^1.2.1", 1986 | "es-object-atoms": "^1.0.0" 1987 | }, 1988 | "engines": { 1989 | "node": ">= 0.4" 1990 | }, 1991 | "funding": { 1992 | "url": "https://github.com/sponsors/ljharb" 1993 | } 1994 | }, 1995 | "node_modules/once": { 1996 | "version": "1.4.0", 1997 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1998 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1999 | "dev": true, 2000 | "license": "ISC", 2001 | "dependencies": { 2002 | "wrappy": "1" 2003 | } 2004 | }, 2005 | "node_modules/optionator": { 2006 | "version": "0.9.4", 2007 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 2008 | "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 2009 | "dev": true, 2010 | "license": "MIT", 2011 | "dependencies": { 2012 | "deep-is": "^0.1.3", 2013 | "fast-levenshtein": "^2.0.6", 2014 | "levn": "^0.4.1", 2015 | "prelude-ls": "^1.2.1", 2016 | "type-check": "^0.4.0", 2017 | "word-wrap": "^1.2.5" 2018 | }, 2019 | "engines": { 2020 | "node": ">= 0.8.0" 2021 | } 2022 | }, 2023 | "node_modules/p-limit": { 2024 | "version": "3.1.0", 2025 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 2026 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 2027 | "dev": true, 2028 | "license": "MIT", 2029 | "dependencies": { 2030 | "yocto-queue": "^0.1.0" 2031 | }, 2032 | "engines": { 2033 | "node": ">=10" 2034 | }, 2035 | "funding": { 2036 | "url": "https://github.com/sponsors/sindresorhus" 2037 | } 2038 | }, 2039 | "node_modules/p-locate": { 2040 | "version": "5.0.0", 2041 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 2042 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 2043 | "dev": true, 2044 | "license": "MIT", 2045 | "dependencies": { 2046 | "p-limit": "^3.0.2" 2047 | }, 2048 | "engines": { 2049 | "node": ">=10" 2050 | }, 2051 | "funding": { 2052 | "url": "https://github.com/sponsors/sindresorhus" 2053 | } 2054 | }, 2055 | "node_modules/parent-module": { 2056 | "version": "1.0.1", 2057 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 2058 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 2059 | "dev": true, 2060 | "license": "MIT", 2061 | "dependencies": { 2062 | "callsites": "^3.0.0" 2063 | }, 2064 | "engines": { 2065 | "node": ">=6" 2066 | } 2067 | }, 2068 | "node_modules/path-exists": { 2069 | "version": "4.0.0", 2070 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 2071 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 2072 | "dev": true, 2073 | "license": "MIT", 2074 | "engines": { 2075 | "node": ">=8" 2076 | } 2077 | }, 2078 | "node_modules/path-is-absolute": { 2079 | "version": "1.0.1", 2080 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 2081 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 2082 | "dev": true, 2083 | "license": "MIT", 2084 | "engines": { 2085 | "node": ">=0.10.0" 2086 | } 2087 | }, 2088 | "node_modules/path-key": { 2089 | "version": "3.1.1", 2090 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 2091 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 2092 | "dev": true, 2093 | "license": "MIT", 2094 | "engines": { 2095 | "node": ">=8" 2096 | } 2097 | }, 2098 | "node_modules/path-parse": { 2099 | "version": "1.0.7", 2100 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 2101 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 2102 | "dev": true, 2103 | "license": "MIT" 2104 | }, 2105 | "node_modules/possible-typed-array-names": { 2106 | "version": "1.0.0", 2107 | "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", 2108 | "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", 2109 | "dev": true, 2110 | "license": "MIT", 2111 | "engines": { 2112 | "node": ">= 0.4" 2113 | } 2114 | }, 2115 | "node_modules/prelude-ls": { 2116 | "version": "1.2.1", 2117 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 2118 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 2119 | "dev": true, 2120 | "license": "MIT", 2121 | "engines": { 2122 | "node": ">= 0.8.0" 2123 | } 2124 | }, 2125 | "node_modules/punycode": { 2126 | "version": "2.3.1", 2127 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 2128 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 2129 | "dev": true, 2130 | "license": "MIT", 2131 | "engines": { 2132 | "node": ">=6" 2133 | } 2134 | }, 2135 | "node_modules/queue-microtask": { 2136 | "version": "1.2.3", 2137 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2138 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 2139 | "dev": true, 2140 | "funding": [ 2141 | { 2142 | "type": "github", 2143 | "url": "https://github.com/sponsors/feross" 2144 | }, 2145 | { 2146 | "type": "patreon", 2147 | "url": "https://www.patreon.com/feross" 2148 | }, 2149 | { 2150 | "type": "consulting", 2151 | "url": "https://feross.org/support" 2152 | } 2153 | ], 2154 | "license": "MIT" 2155 | }, 2156 | "node_modules/regexp.prototype.flags": { 2157 | "version": "1.5.2", 2158 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", 2159 | "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", 2160 | "dev": true, 2161 | "license": "MIT", 2162 | "dependencies": { 2163 | "call-bind": "^1.0.6", 2164 | "define-properties": "^1.2.1", 2165 | "es-errors": "^1.3.0", 2166 | "set-function-name": "^2.0.1" 2167 | }, 2168 | "engines": { 2169 | "node": ">= 0.4" 2170 | }, 2171 | "funding": { 2172 | "url": "https://github.com/sponsors/ljharb" 2173 | } 2174 | }, 2175 | "node_modules/resolve": { 2176 | "version": "1.22.8", 2177 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 2178 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 2179 | "dev": true, 2180 | "license": "MIT", 2181 | "dependencies": { 2182 | "is-core-module": "^2.13.0", 2183 | "path-parse": "^1.0.7", 2184 | "supports-preserve-symlinks-flag": "^1.0.0" 2185 | }, 2186 | "bin": { 2187 | "resolve": "bin/resolve" 2188 | }, 2189 | "funding": { 2190 | "url": "https://github.com/sponsors/ljharb" 2191 | } 2192 | }, 2193 | "node_modules/resolve-from": { 2194 | "version": "4.0.0", 2195 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 2196 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 2197 | "dev": true, 2198 | "license": "MIT", 2199 | "engines": { 2200 | "node": ">=4" 2201 | } 2202 | }, 2203 | "node_modules/reusify": { 2204 | "version": "1.0.4", 2205 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 2206 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 2207 | "dev": true, 2208 | "license": "MIT", 2209 | "engines": { 2210 | "iojs": ">=1.0.0", 2211 | "node": ">=0.10.0" 2212 | } 2213 | }, 2214 | "node_modules/rimraf": { 2215 | "version": "3.0.2", 2216 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 2217 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 2218 | "deprecated": "Rimraf versions prior to v4 are no longer supported", 2219 | "dev": true, 2220 | "license": "ISC", 2221 | "dependencies": { 2222 | "glob": "^7.1.3" 2223 | }, 2224 | "bin": { 2225 | "rimraf": "bin.js" 2226 | }, 2227 | "funding": { 2228 | "url": "https://github.com/sponsors/isaacs" 2229 | } 2230 | }, 2231 | "node_modules/run-parallel": { 2232 | "version": "1.2.0", 2233 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 2234 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 2235 | "dev": true, 2236 | "funding": [ 2237 | { 2238 | "type": "github", 2239 | "url": "https://github.com/sponsors/feross" 2240 | }, 2241 | { 2242 | "type": "patreon", 2243 | "url": "https://www.patreon.com/feross" 2244 | }, 2245 | { 2246 | "type": "consulting", 2247 | "url": "https://feross.org/support" 2248 | } 2249 | ], 2250 | "license": "MIT", 2251 | "dependencies": { 2252 | "queue-microtask": "^1.2.2" 2253 | } 2254 | }, 2255 | "node_modules/safe-array-concat": { 2256 | "version": "1.1.2", 2257 | "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", 2258 | "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", 2259 | "dev": true, 2260 | "license": "MIT", 2261 | "dependencies": { 2262 | "call-bind": "^1.0.7", 2263 | "get-intrinsic": "^1.2.4", 2264 | "has-symbols": "^1.0.3", 2265 | "isarray": "^2.0.5" 2266 | }, 2267 | "engines": { 2268 | "node": ">=0.4" 2269 | }, 2270 | "funding": { 2271 | "url": "https://github.com/sponsors/ljharb" 2272 | } 2273 | }, 2274 | "node_modules/safe-regex-test": { 2275 | "version": "1.0.3", 2276 | "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", 2277 | "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", 2278 | "dev": true, 2279 | "license": "MIT", 2280 | "dependencies": { 2281 | "call-bind": "^1.0.6", 2282 | "es-errors": "^1.3.0", 2283 | "is-regex": "^1.1.4" 2284 | }, 2285 | "engines": { 2286 | "node": ">= 0.4" 2287 | }, 2288 | "funding": { 2289 | "url": "https://github.com/sponsors/ljharb" 2290 | } 2291 | }, 2292 | "node_modules/seek-bzip": { 2293 | "version": "2.0.0", 2294 | "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", 2295 | "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", 2296 | "license": "MIT", 2297 | "dependencies": { 2298 | "commander": "^6.0.0" 2299 | }, 2300 | "bin": { 2301 | "seek-bunzip": "bin/seek-bunzip", 2302 | "seek-table": "bin/seek-bzip-table" 2303 | } 2304 | }, 2305 | "node_modules/semver": { 2306 | "version": "6.3.1", 2307 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 2308 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 2309 | "dev": true, 2310 | "license": "ISC", 2311 | "bin": { 2312 | "semver": "bin/semver.js" 2313 | } 2314 | }, 2315 | "node_modules/set-function-length": { 2316 | "version": "1.2.2", 2317 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 2318 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 2319 | "dev": true, 2320 | "license": "MIT", 2321 | "dependencies": { 2322 | "define-data-property": "^1.1.4", 2323 | "es-errors": "^1.3.0", 2324 | "function-bind": "^1.1.2", 2325 | "get-intrinsic": "^1.2.4", 2326 | "gopd": "^1.0.1", 2327 | "has-property-descriptors": "^1.0.2" 2328 | }, 2329 | "engines": { 2330 | "node": ">= 0.4" 2331 | } 2332 | }, 2333 | "node_modules/set-function-name": { 2334 | "version": "2.0.2", 2335 | "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", 2336 | "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", 2337 | "dev": true, 2338 | "license": "MIT", 2339 | "dependencies": { 2340 | "define-data-property": "^1.1.4", 2341 | "es-errors": "^1.3.0", 2342 | "functions-have-names": "^1.2.3", 2343 | "has-property-descriptors": "^1.0.2" 2344 | }, 2345 | "engines": { 2346 | "node": ">= 0.4" 2347 | } 2348 | }, 2349 | "node_modules/shebang-command": { 2350 | "version": "2.0.0", 2351 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2352 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2353 | "dev": true, 2354 | "license": "MIT", 2355 | "dependencies": { 2356 | "shebang-regex": "^3.0.0" 2357 | }, 2358 | "engines": { 2359 | "node": ">=8" 2360 | } 2361 | }, 2362 | "node_modules/shebang-regex": { 2363 | "version": "3.0.0", 2364 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2365 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2366 | "dev": true, 2367 | "license": "MIT", 2368 | "engines": { 2369 | "node": ">=8" 2370 | } 2371 | }, 2372 | "node_modules/side-channel": { 2373 | "version": "1.0.6", 2374 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 2375 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 2376 | "dev": true, 2377 | "license": "MIT", 2378 | "dependencies": { 2379 | "call-bind": "^1.0.7", 2380 | "es-errors": "^1.3.0", 2381 | "get-intrinsic": "^1.2.4", 2382 | "object-inspect": "^1.13.1" 2383 | }, 2384 | "engines": { 2385 | "node": ">= 0.4" 2386 | }, 2387 | "funding": { 2388 | "url": "https://github.com/sponsors/ljharb" 2389 | } 2390 | }, 2391 | "node_modules/string.prototype.trim": { 2392 | "version": "1.2.9", 2393 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", 2394 | "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", 2395 | "dev": true, 2396 | "license": "MIT", 2397 | "dependencies": { 2398 | "call-bind": "^1.0.7", 2399 | "define-properties": "^1.2.1", 2400 | "es-abstract": "^1.23.0", 2401 | "es-object-atoms": "^1.0.0" 2402 | }, 2403 | "engines": { 2404 | "node": ">= 0.4" 2405 | }, 2406 | "funding": { 2407 | "url": "https://github.com/sponsors/ljharb" 2408 | } 2409 | }, 2410 | "node_modules/string.prototype.trimend": { 2411 | "version": "1.0.8", 2412 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", 2413 | "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", 2414 | "dev": true, 2415 | "license": "MIT", 2416 | "dependencies": { 2417 | "call-bind": "^1.0.7", 2418 | "define-properties": "^1.2.1", 2419 | "es-object-atoms": "^1.0.0" 2420 | }, 2421 | "funding": { 2422 | "url": "https://github.com/sponsors/ljharb" 2423 | } 2424 | }, 2425 | "node_modules/string.prototype.trimstart": { 2426 | "version": "1.0.8", 2427 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", 2428 | "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", 2429 | "dev": true, 2430 | "license": "MIT", 2431 | "dependencies": { 2432 | "call-bind": "^1.0.7", 2433 | "define-properties": "^1.2.1", 2434 | "es-object-atoms": "^1.0.0" 2435 | }, 2436 | "engines": { 2437 | "node": ">= 0.4" 2438 | }, 2439 | "funding": { 2440 | "url": "https://github.com/sponsors/ljharb" 2441 | } 2442 | }, 2443 | "node_modules/strip-ansi": { 2444 | "version": "6.0.1", 2445 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2446 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2447 | "dev": true, 2448 | "license": "MIT", 2449 | "dependencies": { 2450 | "ansi-regex": "^5.0.1" 2451 | }, 2452 | "engines": { 2453 | "node": ">=8" 2454 | } 2455 | }, 2456 | "node_modules/strip-bom": { 2457 | "version": "3.0.0", 2458 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 2459 | "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", 2460 | "dev": true, 2461 | "license": "MIT", 2462 | "engines": { 2463 | "node": ">=4" 2464 | } 2465 | }, 2466 | "node_modules/strip-json-comments": { 2467 | "version": "3.1.1", 2468 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 2469 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 2470 | "dev": true, 2471 | "license": "MIT", 2472 | "engines": { 2473 | "node": ">=8" 2474 | }, 2475 | "funding": { 2476 | "url": "https://github.com/sponsors/sindresorhus" 2477 | } 2478 | }, 2479 | "node_modules/supports-color": { 2480 | "version": "7.2.0", 2481 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2482 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2483 | "dev": true, 2484 | "license": "MIT", 2485 | "dependencies": { 2486 | "has-flag": "^4.0.0" 2487 | }, 2488 | "engines": { 2489 | "node": ">=8" 2490 | } 2491 | }, 2492 | "node_modules/supports-preserve-symlinks-flag": { 2493 | "version": "1.0.0", 2494 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 2495 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 2496 | "dev": true, 2497 | "license": "MIT", 2498 | "engines": { 2499 | "node": ">= 0.4" 2500 | }, 2501 | "funding": { 2502 | "url": "https://github.com/sponsors/ljharb" 2503 | } 2504 | }, 2505 | "node_modules/text-table": { 2506 | "version": "0.2.0", 2507 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 2508 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 2509 | "dev": true, 2510 | "license": "MIT" 2511 | }, 2512 | "node_modules/tsconfig-paths": { 2513 | "version": "3.15.0", 2514 | "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", 2515 | "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", 2516 | "dev": true, 2517 | "license": "MIT", 2518 | "dependencies": { 2519 | "@types/json5": "^0.0.29", 2520 | "json5": "^1.0.2", 2521 | "minimist": "^1.2.6", 2522 | "strip-bom": "^3.0.0" 2523 | } 2524 | }, 2525 | "node_modules/type-check": { 2526 | "version": "0.4.0", 2527 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 2528 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 2529 | "dev": true, 2530 | "license": "MIT", 2531 | "dependencies": { 2532 | "prelude-ls": "^1.2.1" 2533 | }, 2534 | "engines": { 2535 | "node": ">= 0.8.0" 2536 | } 2537 | }, 2538 | "node_modules/type-fest": { 2539 | "version": "0.20.2", 2540 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 2541 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 2542 | "dev": true, 2543 | "license": "(MIT OR CC0-1.0)", 2544 | "engines": { 2545 | "node": ">=10" 2546 | }, 2547 | "funding": { 2548 | "url": "https://github.com/sponsors/sindresorhus" 2549 | } 2550 | }, 2551 | "node_modules/typed-array-buffer": { 2552 | "version": "1.0.2", 2553 | "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", 2554 | "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", 2555 | "dev": true, 2556 | "license": "MIT", 2557 | "dependencies": { 2558 | "call-bind": "^1.0.7", 2559 | "es-errors": "^1.3.0", 2560 | "is-typed-array": "^1.1.13" 2561 | }, 2562 | "engines": { 2563 | "node": ">= 0.4" 2564 | } 2565 | }, 2566 | "node_modules/typed-array-byte-length": { 2567 | "version": "1.0.1", 2568 | "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", 2569 | "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", 2570 | "dev": true, 2571 | "license": "MIT", 2572 | "dependencies": { 2573 | "call-bind": "^1.0.7", 2574 | "for-each": "^0.3.3", 2575 | "gopd": "^1.0.1", 2576 | "has-proto": "^1.0.3", 2577 | "is-typed-array": "^1.1.13" 2578 | }, 2579 | "engines": { 2580 | "node": ">= 0.4" 2581 | }, 2582 | "funding": { 2583 | "url": "https://github.com/sponsors/ljharb" 2584 | } 2585 | }, 2586 | "node_modules/typed-array-byte-offset": { 2587 | "version": "1.0.2", 2588 | "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", 2589 | "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", 2590 | "dev": true, 2591 | "license": "MIT", 2592 | "dependencies": { 2593 | "available-typed-arrays": "^1.0.7", 2594 | "call-bind": "^1.0.7", 2595 | "for-each": "^0.3.3", 2596 | "gopd": "^1.0.1", 2597 | "has-proto": "^1.0.3", 2598 | "is-typed-array": "^1.1.13" 2599 | }, 2600 | "engines": { 2601 | "node": ">= 0.4" 2602 | }, 2603 | "funding": { 2604 | "url": "https://github.com/sponsors/ljharb" 2605 | } 2606 | }, 2607 | "node_modules/typed-array-length": { 2608 | "version": "1.0.6", 2609 | "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", 2610 | "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", 2611 | "dev": true, 2612 | "license": "MIT", 2613 | "dependencies": { 2614 | "call-bind": "^1.0.7", 2615 | "for-each": "^0.3.3", 2616 | "gopd": "^1.0.1", 2617 | "has-proto": "^1.0.3", 2618 | "is-typed-array": "^1.1.13", 2619 | "possible-typed-array-names": "^1.0.0" 2620 | }, 2621 | "engines": { 2622 | "node": ">= 0.4" 2623 | }, 2624 | "funding": { 2625 | "url": "https://github.com/sponsors/ljharb" 2626 | } 2627 | }, 2628 | "node_modules/unbox-primitive": { 2629 | "version": "1.0.2", 2630 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", 2631 | "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", 2632 | "dev": true, 2633 | "license": "MIT", 2634 | "dependencies": { 2635 | "call-bind": "^1.0.2", 2636 | "has-bigints": "^1.0.2", 2637 | "has-symbols": "^1.0.3", 2638 | "which-boxed-primitive": "^1.0.2" 2639 | }, 2640 | "funding": { 2641 | "url": "https://github.com/sponsors/ljharb" 2642 | } 2643 | }, 2644 | "node_modules/uri-js": { 2645 | "version": "4.4.1", 2646 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 2647 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 2648 | "dev": true, 2649 | "license": "BSD-2-Clause", 2650 | "dependencies": { 2651 | "punycode": "^2.1.0" 2652 | } 2653 | }, 2654 | "node_modules/which": { 2655 | "version": "2.0.2", 2656 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2657 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2658 | "dev": true, 2659 | "license": "ISC", 2660 | "dependencies": { 2661 | "isexe": "^2.0.0" 2662 | }, 2663 | "bin": { 2664 | "node-which": "bin/node-which" 2665 | }, 2666 | "engines": { 2667 | "node": ">= 8" 2668 | } 2669 | }, 2670 | "node_modules/which-boxed-primitive": { 2671 | "version": "1.0.2", 2672 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 2673 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", 2674 | "dev": true, 2675 | "license": "MIT", 2676 | "dependencies": { 2677 | "is-bigint": "^1.0.1", 2678 | "is-boolean-object": "^1.1.0", 2679 | "is-number-object": "^1.0.4", 2680 | "is-string": "^1.0.5", 2681 | "is-symbol": "^1.0.3" 2682 | }, 2683 | "funding": { 2684 | "url": "https://github.com/sponsors/ljharb" 2685 | } 2686 | }, 2687 | "node_modules/which-typed-array": { 2688 | "version": "1.1.15", 2689 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", 2690 | "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", 2691 | "dev": true, 2692 | "license": "MIT", 2693 | "dependencies": { 2694 | "available-typed-arrays": "^1.0.7", 2695 | "call-bind": "^1.0.7", 2696 | "for-each": "^0.3.3", 2697 | "gopd": "^1.0.1", 2698 | "has-tostringtag": "^1.0.2" 2699 | }, 2700 | "engines": { 2701 | "node": ">= 0.4" 2702 | }, 2703 | "funding": { 2704 | "url": "https://github.com/sponsors/ljharb" 2705 | } 2706 | }, 2707 | "node_modules/word-wrap": { 2708 | "version": "1.2.5", 2709 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 2710 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 2711 | "dev": true, 2712 | "license": "MIT", 2713 | "engines": { 2714 | "node": ">=0.10.0" 2715 | } 2716 | }, 2717 | "node_modules/wrappy": { 2718 | "version": "1.0.2", 2719 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2720 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2721 | "dev": true, 2722 | "license": "ISC" 2723 | }, 2724 | "node_modules/yocto-queue": { 2725 | "version": "0.1.0", 2726 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 2727 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 2728 | "dev": true, 2729 | "license": "MIT", 2730 | "engines": { 2731 | "node": ">=10" 2732 | }, 2733 | "funding": { 2734 | "url": "https://github.com/sponsors/sindresorhus" 2735 | } 2736 | } 2737 | } 2738 | } 2739 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexrad-level-3-data", 3 | "version": "0.6.1", 4 | "description": "Parsing of NEXRAD level 3 data files", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "node testoutput.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/netbymatt/nexrad-level-3-data.git" 12 | }, 13 | "keywords": [ 14 | "NEXRAD", 15 | "level", 16 | "3", 17 | "radar", 18 | "data", 19 | "parsing" 20 | ], 21 | "author": "Matt Walsh", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/netbymatt/nexrad-level-3-data/issues" 25 | }, 26 | "homepage": "https://github.com/netbymatt/nexrad-level-3-data#readme", 27 | "devDependencies": { 28 | "eslint": "^8.0.0", 29 | "eslint-config-airbnb-base": "^15.0.0", 30 | "eslint-plugin-import": "^2.22.1" 31 | }, 32 | "dependencies": { 33 | "seek-bzip": "^2.0.0" 34 | }, 35 | "engines": { 36 | "node": ">=13.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/headers/graphic.js: -------------------------------------------------------------------------------- 1 | const { parser } = require('../packets'); 2 | const graphic22 = require('./graphic22'); 3 | 4 | const parse = (raf) => { 5 | const blockDivider = raf.readShort(); 6 | // for product 62 the block divider is not present and is a packet code 22 7 | if (blockDivider === 22) { 8 | // jump back to allow full parsing of packet 9 | raf.skip(-2); 10 | // call the special packet 22 parser 11 | return graphic22(raf); 12 | } 13 | 14 | const blockId = raf.readShort(); 15 | const blockLength = raf.readInt(); 16 | 17 | // test some known values 18 | if (blockDivider !== -1) throw new Error(`Invalid graphic block divider: ${blockDivider}`); 19 | if (blockId !== 2) throw new Error(`Invalid graphic id: ${blockId}`); 20 | if (blockLength < 1 || blockLength > 65535) throw new Error(`Invalid block length ${blockLength}`); 21 | if ((blockLength + raf.getPos() - 8) > raf.getLength()) throw new Error(`Block length ${blockLength} overruns file length for block id: ${blockId}`); 22 | 23 | const numberPages = raf.readShort(); 24 | 25 | const packets = []; 26 | 27 | if (numberPages < 1 || numberPages > 48 - 1) throw new Error(`Invalid graphic number of pages: ${numberPages}`); 28 | 29 | // read each page 30 | for (let pageNum = 0; pageNum < numberPages; pageNum += 1) { 31 | const pageNumber = raf.readShort(); 32 | const pageLength = raf.readShort(); 33 | 34 | // calculate end byte 35 | const endByte = raf.getPos() + pageLength; 36 | 37 | // test page number 38 | if (pageNum + 1 !== pageNumber) throw new Error(`Invalid page number: ${pageNumber}`); 39 | 40 | // loop through all packets 41 | while (raf.getPos() < endByte) { 42 | packets.push(parser(raf)); 43 | } 44 | } 45 | 46 | return packets; 47 | }; 48 | 49 | // 50 | 51 | module.exports = parse; 52 | -------------------------------------------------------------------------------- /src/headers/graphic22.js: -------------------------------------------------------------------------------- 1 | // parse data in the graphic area as packet 22 and related packets 2 | const { parser } = require('../packets'); 3 | 4 | const parse22 = (raf) => { 5 | let result = { 6 | cells: {}, 7 | }; 8 | // there is no length header so we parse packets until we get a -1 divider 9 | let divider = raf.readShort(); 10 | while (divider !== -1 && raf.getPos() < raf.getLength()) { 11 | raf.skip(-2); 12 | // add parsed data to result 13 | // parse the data 14 | const data = parser(raf); 15 | // one packet 22 (volume times) is returned, add it directly to the output 16 | if (data.volumeTimes) result = { ...result, ...data }; 17 | // multiple packet 21 (cell data) are returned, add to existing cells 18 | if (!data.volumeTimes) result.cells = { ...result.cells, ...data }; 19 | // test for end of file 20 | if (raf.getPos() < raf.getLength()) divider = raf.readShort(); 21 | } 22 | 23 | // skip back final time if there's more data 24 | if (raf.getPos() < raf.getLength()) raf.skip(-2); 25 | return result; 26 | }; 27 | 28 | module.exports = parse22; 29 | -------------------------------------------------------------------------------- /src/headers/message.js: -------------------------------------------------------------------------------- 1 | const parse = (raf) => ({ 2 | 3 | code: raf.readShort(), 4 | julianDate: raf.readShort(), 5 | seconds: raf.readInt(), 6 | length: raf.readInt(), 7 | source: raf.readShort(), 8 | dest: raf.readShort(), 9 | blocks: raf.readShort(), 10 | 11 | }); 12 | 13 | module.exports = parse; 14 | -------------------------------------------------------------------------------- /src/headers/productdescription.js: -------------------------------------------------------------------------------- 1 | const MODE_MAINTENANCE = 0; 2 | const MODE_CLEAN_AIR = 1; 3 | const MODE_PRECIPITATION = 2; 4 | 5 | const parse = (raf, product) => { 6 | const divider = raf.readShort(); 7 | // check fixed data values 8 | if (divider !== -1) throw new Error(`Invalid product description divider: ${divider}`); 9 | 10 | const result = { 11 | abbreviation: product.abbreviation, 12 | description: product.description, 13 | latitude: raf.readInt() / 1000, 14 | longitude: raf.readInt() / 1000, 15 | height: raf.readShort(), 16 | code: raf.readShort(), 17 | mode: raf.readShort(), 18 | vcp: raf.readShort(), 19 | sequenceNumber: raf.readShort(), 20 | volumeScanNumber: raf.readShort(), 21 | volumeScanDate: raf.readShort(), 22 | volumeScanTime: raf.readInt(), 23 | productDate: raf.readShort(), 24 | productTime: raf.readInt(), 25 | // halfwords 27-28 are product dependent 26 | ...(product?.productDescription?.halfwords27_28?.(raf.read(4)) ?? { dependent27_28: raf.read(4) }), 27 | elevationNumber: raf.readShort(), 28 | // halfwords 30-53 are product dependent 29 | ...(product?.productDescription?.halfwords30_53?.(raf.read(48)) ?? { dependent30_53: raf.read(48) }), 30 | version: raf.readByte(), 31 | spotBlank: raf.readByte(), 32 | offsetSymbology: raf.readInt(), 33 | offsetGraphic: raf.readInt(), 34 | offsetTabular: raf.readInt(), 35 | supplemental: product.supplemental, 36 | }; 37 | 38 | return result; 39 | }; 40 | 41 | // 42 | 43 | module.exports = { 44 | parse, 45 | MODE_MAINTENANCE, 46 | MODE_CLEAN_AIR, 47 | MODE_PRECIPITATION, 48 | }; 49 | -------------------------------------------------------------------------------- /src/headers/radialpackets.js: -------------------------------------------------------------------------------- 1 | // register packet parsers 2 | 3 | const { parser } = require('../packets'); 4 | 5 | const parse = (raf, productDescription, layerCount, options) => { 6 | const layers = []; 7 | for (let layerIndex = 0; layerIndex < layerCount; layerIndex += 1) { 8 | // store starting so skipping the block is possible if layer can't be parsed 9 | const startPos = raf.getPos(); 10 | 11 | // read the header 12 | const layerDivider = raf.readShort(); 13 | const layerLength = raf.readInt(); 14 | if (layerDivider !== -1) throw new Error(`Invalid layer divider ${layerDivider} in layer ${layerIndex}`); 15 | if (layerLength + raf.getPos() > raf.getLength()) throw new Error(`Layer size overruns block size for layer ${layerIndex}`); 16 | 17 | try { 18 | const packets = []; 19 | while (raf.getPos() < startPos + layerLength) { 20 | packets.push(parser(raf, productDescription)); 21 | } 22 | // if there's only one packet return it directly, otherwise return the array 23 | if (packets.length === 1) { 24 | layers.push(packets[0]); 25 | } else { 26 | layers.push(packets); 27 | } 28 | } catch (e) { 29 | options.logger.warn(e.stack); 30 | // skip this layer 31 | raf.seek(startPos + layerLength); 32 | layers.push(undefined); 33 | } 34 | } 35 | return layers; 36 | }; 37 | 38 | module.exports = parse; 39 | -------------------------------------------------------------------------------- /src/headers/symbology.js: -------------------------------------------------------------------------------- 1 | const symbologyText = require('./symbologytext'); 2 | // some block ids just have text, this is not well documented so we do our best to parse these 3 | const textSymbologies = [3, 4, 5, 6, 7]; 4 | 5 | const parse = (raf) => { 6 | const blockDivider = raf.readShort(); 7 | const blockId = raf.readShort(); 8 | // block id 6 is undocumented but appears to be text 9 | if (textSymbologies.includes(blockId)) return symbologyText(raf); 10 | const blockLength = raf.readInt(); 11 | 12 | // test some known values 13 | if (blockDivider !== -1) throw new Error(`Invalid symbology block divider: ${blockDivider}`); 14 | if (blockId !== 1) throw new Error(`Invalid symbology id: ${blockId}`); 15 | if ((blockLength + raf.getPos() - 8) > raf.getLength()) throw new Error(`Block length ${blockLength} overruns file length for block id: ${blockId}`); 16 | 17 | const result = { 18 | numberLayers: raf.readShort(), 19 | }; 20 | 21 | return result; 22 | }; 23 | 24 | // 25 | 26 | module.exports = parse; 27 | -------------------------------------------------------------------------------- /src/headers/symbologytext.js: -------------------------------------------------------------------------------- 1 | // block id 6 is undocumented but appears to be text 2 | 3 | const parse = (raf) => { 4 | const pages = []; 5 | let lines = []; 6 | 7 | // loop until a -1 is encountered 8 | let length = raf.readShort(); 9 | do { 10 | while (length !== -1) { 11 | lines.push(raf.readString(length)); 12 | length = raf.readShort(); 13 | } 14 | pages.push(lines); 15 | lines = []; 16 | // catch the end of file 17 | if (raf.getPos() < raf.getLength()) { 18 | length = raf.readShort(); 19 | } else { 20 | length = -1; 21 | } 22 | } while (length === 80); 23 | 24 | // roll back the 4 bytes used to detect the end of the text area 25 | raf.skip(-4); 26 | 27 | return { pages }; 28 | }; 29 | 30 | // 31 | 32 | module.exports = parse; 33 | -------------------------------------------------------------------------------- /src/headers/tabular.js: -------------------------------------------------------------------------------- 1 | const parseMessageHeader = require('./message'); 2 | const { parse: parseProductDescription } = require('./productdescription'); 3 | 4 | const parse = (raf, product) => { 5 | const blockDivider = raf.readShort(); 6 | const blockId = raf.readShort(); 7 | const blockLength = raf.readInt(); 8 | 9 | // test some known values 10 | if (blockDivider !== -1) throw new Error(`Invalid tabular block divider: ${blockDivider}`); 11 | if (blockId !== 3) throw new Error(`Invalid tabular id: ${blockId}`); 12 | if (blockLength < 1 || blockLength > 65535) throw new Error(`Invalid block length ${blockLength}`); 13 | if ((blockLength + raf.getPos() - 8) > raf.getLength()) throw new Error(`Block length ${blockLength} overruns file length for block id: ${blockId}`); 14 | 15 | const messageHeader = parseMessageHeader(raf); 16 | const productDescription = parseProductDescription(raf, product); 17 | const blockDivider2 = raf.readShort(); 18 | 19 | // test some known values 20 | if (blockDivider2 !== -1) throw new Error(`Invalid second tabular block divider: ${blockDivider2}`); 21 | 22 | const result = { 23 | messageHeader, 24 | productDescription, 25 | totalPages: raf.readShort(), 26 | charactersPerLine: raf.readShort(), 27 | pages: [], 28 | }; 29 | 30 | // loop through data until end of page reached 31 | for (let i = 0; i < result.totalPages; i += 1) { 32 | // page string 33 | const lines = []; 34 | let line = ''; 35 | // loop through lines until end of page reached 36 | // read two characters at a time to detect end of page 37 | let chars = raf.readShort(); 38 | while (chars !== -1) { 39 | // not in specification, but appears to be a line ending 40 | if (chars !== 0x0050) { 41 | // write the first character to the line 42 | line += String.fromCharCode(chars >> 8); 43 | // test length 44 | if (line.length % result.charactersPerLine === 0) { lines.push(line); line = ''; } 45 | // write the first character to the line 46 | line += String.fromCharCode(chars & 0x00FF); 47 | // test length 48 | if (line.length % result.charactersPerLine === 0) { lines.push(line); line = ''; } 49 | } 50 | // get next characters 51 | chars = raf.readShort(); 52 | } 53 | result.pages.push(lines); 54 | } 55 | 56 | return result; 57 | }; 58 | 59 | // 60 | 61 | module.exports = parse; 62 | -------------------------------------------------------------------------------- /src/headers/text.js: -------------------------------------------------------------------------------- 1 | // file header as 30 byte string 2 | 3 | const parse = (raf) => { 4 | const text = {}; 5 | text.fileType = raf.readString(6); 6 | // always a space 7 | raf.readString(1); 8 | // radar site id 9 | text.id = raf.readString(4); 10 | // always a space 11 | raf.readString(1); 12 | // ddhhmm day-hour-minute timestamp, returned as a string as a more useful timestamp is contained within the data of the file 13 | text.ddhhmm = raf.readString(6); 14 | // line breaks 15 | raf.readString(3); 16 | // type of data 17 | text.type = raf.readString(3); 18 | // site identifier as 3-letter code 19 | text.id3 = raf.readString(3); 20 | // line breaks 21 | raf.readString(3); 22 | 23 | return text; 24 | }; 25 | 26 | module.exports = parse; 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const bzip = require('seek-bzip'); 2 | const { RandomAccessFile } = require('./randomaccessfile'); 3 | const textHeader = require('./headers/text'); 4 | const messageHeader = require('./headers/message'); 5 | const { parse: productDescription } = require('./headers/productdescription'); 6 | const symbologyHeader = require('./headers/symbology'); 7 | const tabularHeader = require('./headers/tabular'); 8 | const graphicHeader = require('./headers/graphic'); 9 | const radialPackets = require('./headers/radialpackets'); 10 | const { products, productAbbreviations } = require('./products'); 11 | 12 | // parse data provided from string or buffer 13 | const nexradLevel3Data = (file, _options) => { 14 | const options = combineOptions(_options); 15 | 16 | // convert to random access file 17 | const raf = new RandomAccessFile(file); 18 | 19 | // result object 20 | const result = {}; 21 | 22 | // get the header 23 | result.textHeader = textHeader(raf); 24 | 25 | // text header is not accounted for in data description. Note the length here for additional offset calculations 26 | const textHeaderLength = raf.getPos(); 27 | 28 | // test for valid file 29 | if (!result.textHeader.fileType.startsWith('SDUS')) throw new Error(`Incorrect file type header: ${result.textHeader.fileType}`); 30 | if (!productAbbreviations.includes(result.textHeader.type)) throw new Error(`Unsupported product type: ${result.textHeader.type}`); 31 | 32 | // message header 33 | result.messageHeader = messageHeader(raf); 34 | // get the product 35 | const product = products[result.messageHeader.code.toString()]; 36 | 37 | // test for product type again 38 | if (!product) throw new Error(`Unsupported product code: ${result.messageHeader.code}`); 39 | 40 | // product description 41 | result.productDescription = productDescription(raf, product); 42 | 43 | // test for compressed file and decompress 44 | let decompressed; 45 | if (result.productDescription.compressionMethod > 0) { 46 | // store position in file 47 | const rafPos = raf.getPos(); 48 | // get the remainder of the file 49 | const compressed = raf.read(raf.getLength() - raf.getPos()); 50 | const data = bzip.decode(compressed); 51 | // combine the header from the original file with the decompressed data 52 | raf.seek(0); 53 | decompressed = new RandomAccessFile(Buffer.concat([ 54 | raf.read(rafPos), 55 | data, 56 | ])); 57 | decompressed.seek(rafPos); 58 | } else { 59 | // pass file through 60 | decompressed = raf; 61 | } 62 | 63 | // symbology parsing 64 | try { 65 | if (result.productDescription.offsetSymbology !== 0) { 66 | // jump to symbology, convert halfwords to bytes 67 | const offsetSymbologyBytes = textHeaderLength + result.productDescription.offsetSymbology * 2; 68 | // error checking 69 | if (offsetSymbologyBytes > decompressed.getLength()) throw new Error(`Invalid symbology offset: ${result.productDescription.offsetSymbology}`); 70 | decompressed.seek(offsetSymbologyBytes); 71 | 72 | // read the symbology header 73 | result.symbology = symbologyHeader(decompressed); 74 | // read the radial packet header 75 | result.radialPackets = radialPackets(decompressed, result.productDescription, result.symbology.numberLayers, options); 76 | } 77 | } catch (e) { 78 | options.logger.warn(e.stack); 79 | options.logger.warn('Unable to parse symbology data'); 80 | } 81 | 82 | // graphic parsing 83 | try { 84 | if (result.productDescription.offsetGraphic !== 0) { 85 | // jump to graphic, convert halfwords to bytes 86 | const offsetGraphicBytes = textHeaderLength + result.productDescription.offsetGraphic * 2; 87 | // error checking 88 | if (offsetGraphicBytes > decompressed.getLength()) throw new Error(`Invalid graphic offset: ${result.productDescription.offsetGraphic}`); 89 | decompressed.seek(offsetGraphicBytes); 90 | 91 | // read the graphic header 92 | result.graphic = graphicHeader(decompressed); 93 | } 94 | } catch (e) { 95 | options.logger.warn(e.stack); 96 | options.logger.warn('Unable to parse graphic data'); 97 | } 98 | 99 | // tabular parsing 100 | try { 101 | if (result.productDescription.offsetTabular !== 0) { 102 | // jump to tabular, convert halfwords to bytes 103 | const offsetTabularBytes = textHeaderLength + result.productDescription.offsetTabular * 2; 104 | // error checking 105 | if (offsetTabularBytes > decompressed.getLength()) throw new Error(`Invalid tabular offset: ${result.productDescription.offsetTabular}`); 106 | decompressed.seek(offsetTabularBytes); 107 | 108 | // read the tabular header 109 | result.tabular = tabularHeader(decompressed, product); 110 | } 111 | } catch (e) { 112 | options.logger.warn(e.stack); 113 | options.logger.warn('Unable to parse tabular data'); 114 | } 115 | 116 | // get formatted data if it exists 117 | try { 118 | const formatted = product?.formatter?.(result); 119 | if (formatted) result.formatted = formatted; 120 | } catch (e) { 121 | options.logger.warn(e.stack); 122 | options.logger.warn('Unable to parse formatted tabular data'); 123 | } 124 | 125 | return result; 126 | }; 127 | 128 | // combine options and defaults 129 | const combineOptions = (newOptions) => { 130 | let logger = newOptions?.logger ?? console; 131 | if (logger === false) logger = nullLogger; 132 | return { 133 | ...newOptions, logger, 134 | }; 135 | }; 136 | 137 | const nullLogger = { 138 | log: () => {}, 139 | error: () => {}, 140 | warn: () => {}, 141 | }; 142 | 143 | module.exports = nexradLevel3Data; 144 | -------------------------------------------------------------------------------- /src/packets/1.js: -------------------------------------------------------------------------------- 1 | const code = 1; 2 | const description = 'Text and Special Symbol Packets'; 3 | 4 | const parser = (raf) => { 5 | // packet header 6 | const packetCode = raf.readUShort(); 7 | const lengthOfBlock = raf.readShort(); 8 | 9 | // test packet code 10 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 11 | 12 | // parse the data 13 | const result = { 14 | iStartingPoint: raf.readShort(), 15 | jStartingPoint: raf.readShort(), 16 | }; 17 | // also providethe packet code in hex 18 | result.packetCodeHex = packetCode.toString(16); 19 | 20 | // read the result length 21 | result.text = raf.readString(lengthOfBlock - 4); 22 | 23 | return result; 24 | }; 25 | 26 | module.exports = { 27 | code, 28 | description, 29 | parser, 30 | }; 31 | -------------------------------------------------------------------------------- /src/packets/10.js: -------------------------------------------------------------------------------- 1 | const code = 16; 2 | const description = 'Digital Radial Data Array Packet'; 3 | 4 | const parser = (raf, productDescription) => { 5 | // packet header 6 | const packetCode = raf.readUShort(); 7 | 8 | // test packet code 9 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 10 | 11 | // parse the data 12 | const result = { 13 | firstBin: raf.readShort(), 14 | numberBins: raf.readShort(), 15 | iSweepCenter: raf.readShort(), 16 | jSweepCenter: raf.readShort(), 17 | rangeScale: raf.readShort() / 1000, 18 | numberRadials: raf.readShort(), 19 | }; 20 | // also providethe packet code in hex 21 | result.packetCodeHex = packetCode.toString(16); 22 | 23 | // set up scaling or defaults 24 | const scaling = { 25 | scale: productDescription?.plot?.scale ?? 1, 26 | offset: productDescription?.plot?.offset ?? 0, 27 | }; 28 | 29 | // create a lookup table mapping raw bin values to scaled values 30 | const scaled = []; 31 | let start = 0; 32 | // if a plot object is defined add scaling options 33 | if (productDescription?.plot?.leadingFlags?.noData === 0) { 34 | start = 1; 35 | scaled[0] = null; 36 | } 37 | if (productDescription?.plot?.maxDataValue !== undefined) { 38 | for (let i = start; i <= productDescription.plot.maxDataValue; i += 1) { 39 | scaled.push(((i - scaling.offset) / scaling.scale)); 40 | } 41 | } else if (productDescription?.plot?.dataLevels !== undefined) { 42 | // below threshold and missing are null 43 | scaled[0] = null; 44 | scaled[1] = null; 45 | for (let i = 2; i <= productDescription.plot.dataLevels; i += 1) { 46 | scaled[i] = productDescription.plot.minimumDataValue + (i * productDescription.plot.dataIncrement); 47 | } 48 | } 49 | 50 | // loop through the radials and bins 51 | // return a structure of [radial][bin] 52 | // radials provides scaled values per the product's scaling, radialsRaw provides bytes as read from the file 53 | const radials = []; 54 | const radialsRaw = []; 55 | for (let r = 0; r < result.numberRadials; r += 1) { 56 | const bytesInRadial = raf.readShort(); 57 | const radial = { 58 | startAngle: raf.readShort() / 10, 59 | angleDelta: raf.readShort() / 10, 60 | bins: [], 61 | }; 62 | const radialRaw = { ...radial, bins: [] }; 63 | for (let i = 0; i < result.numberBins; i += 1) { 64 | const value = raf.readByte(); 65 | radial.bins.push(scaled[value]); 66 | radialRaw.bins.push(value); 67 | } 68 | radials.push(radial); 69 | radialsRaw.push(radialRaw); 70 | // must end on a halfword boundary, skip any additional data if required 71 | if (bytesInRadial !== result.numberBins) raf.skip(bytesInRadial - result.numberBins); 72 | } 73 | result.radials = radials; 74 | result.radialsRaw = radialsRaw; 75 | 76 | return result; 77 | }; 78 | 79 | module.exports = { 80 | code, 81 | description, 82 | parser, 83 | }; 84 | -------------------------------------------------------------------------------- /src/packets/13.js: -------------------------------------------------------------------------------- 1 | const code = 19; 2 | const description = 'Special Graphic Symbol Packet'; 3 | 4 | // feature key 5 | const featureKey = { 6 | 1: 'mesocyclone (extrapolated)', 7 | 3: 'mesocyclone (persistent, new or increasing)', 8 | 5: 'TVS (extrapolated)', 9 | 6: 'ETVS (extrapolated)', 10 | 7: 'TVS (persistent, new or increasing)', 11 | 8: 'ETVS (persistent, new or increasing)', 12 | 9: 'MDA Circulation with Strength Rank >= 5 AND with a Base Height <= 1 km ARL or with its base on the lowest elevation angle', 13 | 10: 'MDA Circulation with Strength Rank >= 5 AND with a Base Height > 1 km ARL AND that Base is not on the lowest elevation angle', 14 | 11: ' MDA Circulation with Strength Rank< 5', 15 | }; 16 | 17 | const parser = (raf) => { 18 | // packet header 19 | const packetCode = raf.readUShort(); 20 | const lengthOfBlock = raf.readShort(); 21 | 22 | // test packet code 23 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 24 | 25 | // parse the data 26 | const result = { 27 | points: [], 28 | }; 29 | // also providethe packet code in hex 30 | result.packetCodeHex = packetCode.toString(16); 31 | 32 | // read all special symbols 33 | let i = 0; 34 | for (i = 0; (i < lengthOfBlock) && (i + 8 < lengthOfBlock); i += 8) { 35 | const iStartingPoint = raf.readShort(); 36 | const jStartingPoint = raf.readShort(); 37 | const pointFeatureType = raf.readShort(); 38 | const pointFeatureAttribute = raf.readShort(); 39 | result.points.push({ 40 | iStartingPoint, 41 | jStartingPoint, 42 | pointFeatureType, 43 | pointFeatureAttribute, 44 | }); 45 | } 46 | 47 | // skip past extra data 48 | raf.skip(result.lengthOfBlock - i); 49 | 50 | return result; 51 | }; 52 | 53 | module.exports = { 54 | code, 55 | description, 56 | parser, 57 | supplemental: { featureKey }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/packets/14.js: -------------------------------------------------------------------------------- 1 | const code = 20; 2 | const description = 'Special Graphic Symbol Packet'; 3 | 4 | // feature key 5 | const featureKey = { 6 | 1: 'mesocyclone (extrapolated)', 7 | 3: 'mesocyclone (persistent, new or increasing)', 8 | 5: 'TVS (extrapolated)', 9 | 6: 'ETVS (extrapolated)', 10 | 7: 'TVS (persistent, new or increasing)', 11 | 8: 'ETVS (persistent, new or increasing)', 12 | 9: 'MDA Circulation with Strength Rank >= 5 AND with a Base Height <= 1 km ARL or with its base on the lowest elevation angle', 13 | 10: 'MDA Circulation with Strength Rank >= 5 AND with a Base Height > 1 km ARL AND that Base is not on the lowest elevation angle', 14 | 11: ' MDA Circulation with Strength Rank< 5', 15 | }; 16 | 17 | const parser = (raf) => { 18 | // packet header 19 | const packetCode = raf.readUShort(); 20 | const lengthOfBlock = raf.readShort(); 21 | 22 | // test packet code 23 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 24 | 25 | // parse the data 26 | const result = { 27 | points: [], 28 | }; 29 | // also providethe packet code in hex 30 | result.packetCodeHex = packetCode.toString(16); 31 | 32 | // read all special symbols 33 | let i = 0; 34 | for (i = 0; (i < lengthOfBlock) && (i + 8 <= lengthOfBlock); i += 8) { 35 | const iStartingPoint = raf.readShort(); 36 | const jStartingPoint = raf.readShort(); 37 | const pointFeatureType = raf.readShort(); 38 | const pointFeatureAttribute = raf.readShort() / 4; // radius in kilometers 39 | result.points.push({ 40 | iStartingPoint, 41 | jStartingPoint, 42 | pointFeatureType, 43 | pointFeatureAttribute, 44 | }); 45 | } 46 | 47 | // skip past extra data 48 | raf.skip(result.lengthOfBlock - i); 49 | 50 | return result; 51 | }; 52 | 53 | module.exports = { 54 | code, 55 | description, 56 | parser, 57 | supplemental: { featureKey }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/packets/15.js: -------------------------------------------------------------------------------- 1 | const code = 21; 2 | const description = 'Special Graphic Symbol Packet'; 3 | const { ijToAzDeg } = require('./utilities/ij'); 4 | 5 | // scaling data for each trend code 6 | const trendCodeScale = [ 7 | null, // index zero is unused 8 | 100.0, // feet 9 | 100.0, // feet 10 | 100.0, // feet 11 | 1.00, // % 12 | 1.00, // % 13 | 1.00, // kg/m^2 14 | 1.00, // dBz 15 | 100.0, // feet 16 | ]; 17 | 18 | // trend code meaning 19 | const trendCodes = { 20 | 1: 'Cell top, feet', 21 | 2: 'Cell base, feet', 22 | 3: 'Max ref height, feet', 23 | 4: 'Probability of Hail, %', 24 | 5: 'Probability of Severe Hail, %', 25 | 6: 'Cell based VIL, kg/m^2', 26 | 7: 'Max ref, dBz', 27 | 8: 'Centroid height, feet', 28 | }; 29 | 30 | const parser = (raf) => { 31 | // packet header 32 | const packetCode = raf.readUShort(); 33 | const packetLength = raf.readShort(); 34 | 35 | // test packet code 36 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 37 | const startPos = raf.getPos(); 38 | const endPos = startPos + packetLength; 39 | 40 | // parse the data 41 | const cellId = raf.readString(2); 42 | const result = { 43 | iPosition: raf.readShort(), 44 | jPosition: raf.readShort(), 45 | trends: [], 46 | }; 47 | 48 | // provide convenience conversions 49 | const converted = ijToAzDeg(result.iPosition, result.jPosition); 50 | result.nm = converted.nm; 51 | result.deg = converted.deg; 52 | 53 | // read the trend packet 54 | // end is calculated from length 55 | while (raf.getPos() < endPos) { 56 | const trendCode = raf.readShort(); 57 | const numberVolumes = raf.readByte(); 58 | // pointer is 1-based, shift to align with javascript 0-based array 59 | const latestVolumePointer = raf.readByte() - 1; 60 | 61 | const trend = { 62 | type: trendCodes[trendCode], 63 | data: [], 64 | }; 65 | 66 | // add a friendly trend code name 67 | trend.type = trendCodes[trendCode]; 68 | 69 | // read data for each volume and scale 70 | for (let j = 0; j < numberVolumes; j += 1) { 71 | let value = raf.readShort(); 72 | // test codes 1 and 2 have a special considerating for scaling, subtract 1000 from values over 700 73 | if ([1, 2].includes(trendCode) && value > 700) value -= 1000; 74 | trend.data.push(value * trendCodeScale[trendCode]); 75 | } 76 | // reshuffle the array with the newest data first 77 | trend.data = [...trend.data.slice(latestVolumePointer + 1), ...trend.data.slice(0, latestVolumePointer + 1)].reverse(); 78 | 79 | // index trends by code 80 | result.trends[trendCode] = trend; 81 | } 82 | 83 | return { [cellId]: result }; 84 | }; 85 | 86 | module.exports = { 87 | code, 88 | description, 89 | parser, 90 | }; 91 | -------------------------------------------------------------------------------- /src/packets/16.js: -------------------------------------------------------------------------------- 1 | const code = 22; 2 | const description = 'Cell Trend Data Packet'; 3 | 4 | const parser = (raf) => { 5 | // packet header 6 | const packetCode = raf.readUShort(); 7 | 8 | // test packet code 9 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 10 | 11 | // parse the data 12 | const numberVolumes = raf.readByte(); 13 | // pointer is 1-based, shift to align with javascript 0-based array 14 | const latestVolumePointer = raf.readByte() - 1; 15 | const result = { 16 | volumeTimes: [], 17 | }; 18 | 19 | // read the result length 20 | for (let i = 0; i < numberVolumes; i += 1) { 21 | result.volumeTimes.push(raf.readShort()); 22 | } 23 | // reshuffle the array with the newest data first 24 | result.volumeTimes = [...result.volumeTimes.slice(latestVolumePointer + 1), ...result.volumeTimes.slice(0, latestVolumePointer + 1)].reverse(); 25 | 26 | return result; 27 | }; 28 | 29 | module.exports = { 30 | code, 31 | description, 32 | parser, 33 | }; 34 | -------------------------------------------------------------------------------- /src/packets/17.js: -------------------------------------------------------------------------------- 1 | const code = 23; 2 | const description = 'Special Graphic Symbol Packet'; 3 | 4 | const parser = (raf) => { 5 | // must require dynamically to avoid circular dependency when not yet fully executed 6 | // eslint-disable-next-line global-require 7 | const { parser: packetParser } = require('.'); 8 | // packet header 9 | const packetCode = raf.readUShort(); 10 | const lengthOfBlock = raf.readShort(); 11 | 12 | // parse the data as a series of packets 13 | const endPos = raf.getPos() + lengthOfBlock; 14 | const result = { 15 | packets: [], 16 | }; 17 | while (raf.getPos() < endPos) { 18 | result.packets.push(packetParser(raf)); 19 | } 20 | 21 | // also provide the packet code in hex 22 | result.packetCodeHex = packetCode.toString(16); 23 | 24 | return result; 25 | }; 26 | 27 | module.exports = { 28 | code, 29 | description, 30 | parser, 31 | }; 32 | -------------------------------------------------------------------------------- /src/packets/18.js: -------------------------------------------------------------------------------- 1 | const code = 24; 2 | const description = 'Special Graphic Symbol Packet'; 3 | 4 | // uses the same parser as 23 (0x17) 5 | const { parser } = require('./17'); 6 | 7 | module.exports = { 8 | code, 9 | description, 10 | parser, 11 | }; 12 | -------------------------------------------------------------------------------- /src/packets/19.js: -------------------------------------------------------------------------------- 1 | const code = 25; 2 | const description = 'Special Graphic Symbol Packet'; 3 | 4 | // uses the same parser as 23 (0x17) 5 | const { parser } = require('./17'); 6 | 7 | module.exports = { 8 | code, 9 | description, 10 | parser, 11 | }; 12 | -------------------------------------------------------------------------------- /src/packets/2.js: -------------------------------------------------------------------------------- 1 | const code = 2; 2 | const description = 'Text and Special Symbol Packets'; 3 | 4 | const parser = (raf) => { 5 | // packet header 6 | const packetCode = raf.readUShort(); 7 | const lengthOfBlock = raf.readShort(); 8 | 9 | // test packet code 10 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 11 | 12 | // parse the data 13 | const result = { 14 | iStartingPoint: raf.readShort(), 15 | jStartingPoint: raf.readShort(), 16 | text: raf.readString(lengthOfBlock - 4), 17 | }; 18 | // also providethe packet code in hex 19 | result.packetCodeHex = packetCode.toString(16); 20 | 21 | return result; 22 | }; 23 | 24 | module.exports = { 25 | code, 26 | description, 27 | parser, 28 | }; 29 | -------------------------------------------------------------------------------- /src/packets/32.js: -------------------------------------------------------------------------------- 1 | const code = 32; 2 | const description = 'Special Graphic Symbol Packet'; 3 | 4 | // feature key 5 | const featureKey = { 6 | 1: 'mesocyclone (extrapolated)', 7 | 3: 'mesocyclone (persistent, new or increasing)', 8 | 5: 'TVS (extrapolated)', 9 | 6: 'ETVS (extrapolated)', 10 | 7: 'TVS (persistent, new or increasing)', 11 | 8: 'ETVS (persistent, new or increasing)', 12 | 9: 'MDA Circulation with Strength Rank >= 5 AND with a Base Height <= 1 km ARL or with its base on the lowest elevation angle', 13 | 10: 'MDA Circulation with Strength Rank >= 5 AND with a Base Height > 1 km ARL AND that Base is not on the lowest elevation angle', 14 | 11: ' MDA Circulation with Strength Rank< 5', 15 | }; 16 | 17 | const parser = (raf) => { 18 | // packet header 19 | const packetCode = raf.readUShort(); 20 | const lengthOfBlock = raf.readShort(); 21 | 22 | // test packet code 23 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 24 | 25 | // parse the data 26 | const result = { 27 | points: [], 28 | }; 29 | // also providethe packet code in hex 30 | result.packetCodeHex = packetCode.toString(16); 31 | 32 | // read all special symbols 33 | let i = 0; 34 | for (i = 0; (i < lengthOfBlock) && (i + 8 < lengthOfBlock); i += 8) { 35 | const iStartingPoint = raf.readShort(); 36 | const jStartingPoint = raf.readShort(); 37 | const pointFeatureType = raf.readShort(); 38 | const pointFeatureAttribute = raf.readShort(); 39 | result.points.push({ 40 | iStartingPoint, 41 | jStartingPoint, 42 | pointFeatureType, 43 | pointFeatureAttribute, 44 | }); 45 | } 46 | 47 | // skip past extra data 48 | raf.skip(result.lengthOfBlock - i); 49 | 50 | return result; 51 | }; 52 | 53 | module.exports = { 54 | code, 55 | description, 56 | parser, 57 | supplemental: { featureKey }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/packets/6.js: -------------------------------------------------------------------------------- 1 | const code = 6; 2 | const description = 'Linked Vector Packet'; 3 | 4 | // i and j = -2048 < i,j < 2047 5 | 6 | const parser = (raf) => { 7 | // packet header 8 | const packetCode = raf.readUShort(); 9 | const lengthOfBlock = raf.readShort(); 10 | 11 | // test packet code 12 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 13 | 14 | // parse the data 15 | const result = { 16 | iStartingPoint: raf.readShort(), 17 | jStartingPoint: raf.readShort(), 18 | vectors: [], 19 | }; 20 | // also provide the packet code in hex 21 | result.packetCodeHex = packetCode.toString(16); 22 | 23 | // calculate end byte (off by 4 from starting point) 24 | const endByte = raf.getPos() + lengthOfBlock - 4; 25 | 26 | // read vectors for length of packet 27 | while (raf.getPos() < endByte) { 28 | // read start and end coordinate pairs per vector 29 | result.vectors.push({ 30 | i: raf.readShort(), 31 | j: raf.readShort(), 32 | }); 33 | } 34 | 35 | return result; 36 | }; 37 | 38 | module.exports = { 39 | code, 40 | description, 41 | parser, 42 | }; 43 | -------------------------------------------------------------------------------- /src/packets/8.js: -------------------------------------------------------------------------------- 1 | const code = 8; 2 | const description = 'Text and Special Symbol Packets'; 3 | 4 | const parser = (raf) => { 5 | // packet header 6 | const packetCode = raf.readUShort(); 7 | const lengthOfBlock = raf.readShort(); 8 | 9 | // parse the data 10 | const result = { 11 | color: raf.readShort(), 12 | iStartingPoint: raf.readShort(), 13 | jStartingPoint: raf.readShort(), 14 | }; 15 | // also provide the packet code in hex 16 | result.packetCodeHex = packetCode.toString(16); 17 | 18 | // read the result length 19 | result.text = raf.readString(lengthOfBlock - 6); 20 | 21 | return result; 22 | }; 23 | 24 | module.exports = { 25 | code, 26 | description, 27 | parser, 28 | }; 29 | -------------------------------------------------------------------------------- /src/packets/a.js: -------------------------------------------------------------------------------- 1 | const code = 10; 2 | const description = 'Unlinked Vector Packet'; 3 | 4 | // i and j = -2048 < i,j < 2047 5 | 6 | const parser = (raf) => { 7 | // packet header 8 | const packetCode = raf.readUShort(); 9 | const lengthOfBlock = raf.readShort(); 10 | 11 | // test packet code 12 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 13 | 14 | // parse the data 15 | const result = { 16 | color: raf.readShort(), 17 | vectors: [], 18 | }; 19 | // also provide the packet code in hex 20 | result.packetCodeHex = packetCode.toString(16); 21 | 22 | // calculate end byte (off by 2 from result.color) 23 | const endByte = raf.getPos() + lengthOfBlock - 2; 24 | 25 | // read vectors for length of packet 26 | while (raf.getPos() < endByte) { 27 | // read start and end coordinate pairs per vector 28 | result.vectors.push({ 29 | start: { 30 | i: raf.readShort(), 31 | j: raf.readShort(), 32 | 33 | }, 34 | end: { 35 | i: raf.readShort(), 36 | j: raf.readShort(), 37 | }, 38 | }); 39 | } 40 | 41 | return result; 42 | }; 43 | 44 | module.exports = { 45 | code, 46 | description, 47 | parser, 48 | }; 49 | -------------------------------------------------------------------------------- /src/packets/af1f.js: -------------------------------------------------------------------------------- 1 | const code = 0xaf1f; 2 | const description = 'Radial Data Packet (16 Data Levels)'; 3 | const rle = require('./utilities/rle'); 4 | 5 | const parser = (raf) => { 6 | // packet header 7 | const packetCode = raf.readUShort(); 8 | 9 | // test packet code 10 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 11 | 12 | // parse the data 13 | const result = { 14 | firstBin: raf.readShort(), 15 | numberBins: raf.readShort(), 16 | iSweepCenter: raf.readShort(), 17 | jSweepCenter: raf.readShort(), 18 | rangeScale: raf.readShort() / 1000, 19 | numRadials: raf.readShort(), 20 | }; 21 | // also providethe packet code in hex 22 | result.packetCodeHex = packetCode.toString(16); 23 | 24 | // loop through the radials and bins 25 | // return a structure of [radial][bin] 26 | const radials = []; 27 | for (let r = 0; r < result.numRadials; r += 1) { 28 | // get the rle length 29 | const rleLength = raf.readShort() * 2; 30 | const radial = { 31 | startAngle: raf.readShort() / 10, 32 | angleDelta: raf.readShort() / 10, 33 | bins: [], 34 | }; 35 | for (let i = 0; i < rleLength; i += 1) { 36 | radial.bins.push(...(rle.expand4_4(raf.readByte()))); 37 | } 38 | radials.push(radial); 39 | } 40 | result.radials = radials; 41 | 42 | return result; 43 | }; 44 | 45 | module.exports = { 46 | code, 47 | description, 48 | parser, 49 | }; 50 | -------------------------------------------------------------------------------- /src/packets/c.js: -------------------------------------------------------------------------------- 1 | const code = 12; 2 | const description = 'Tornado Vortex Signautre'; 3 | 4 | const parser = (raf) => { 5 | // packet header 6 | const packetCode = raf.readUShort(); 7 | const lengthOfBlock = raf.readShort(); 8 | 9 | // test packet code 10 | if (packetCode !== code) throw new Error(`Packet codes do not match ${code} !== ${packetCode}`); 11 | 12 | // parse the data 13 | const result = { 14 | points: [], 15 | }; 16 | // also providethe packet code in hex 17 | result.packetCodeHex = packetCode.toString(16); 18 | 19 | // read all special symbols 20 | let i = 0; 21 | for (i = 0; (i < lengthOfBlock) && (i + 4 <= lengthOfBlock); i += 4) { 22 | const iStartingPoint = raf.readShort(); 23 | const jStartingPoint = raf.readShort(); 24 | result.points.push({ 25 | iStartingPoint, 26 | jStartingPoint, 27 | }); 28 | } 29 | 30 | // skip past extra data 31 | raf.skip(result.lengthOfBlock - i); 32 | 33 | return result; 34 | }; 35 | 36 | module.exports = { 37 | code, 38 | description, 39 | parser, 40 | }; 41 | -------------------------------------------------------------------------------- /src/packets/f.js: -------------------------------------------------------------------------------- 1 | const code = 15; 2 | const description = 'Special Graphic Symbol Packet'; 3 | 4 | const parser = (raf) => { 5 | // packet header 6 | const packetCode = raf.readUShort(); 7 | const lengthOfBlock = raf.readShort(); 8 | 9 | // parse the data 10 | const result = { 11 | symbols: [], 12 | }; 13 | const endPos = raf.getPos() + lengthOfBlock; 14 | while (raf.getPos() < endPos) { 15 | result.symbols.push({ 16 | iStartingPoint: raf.readShort(), 17 | jStartingPoint: raf.readShort(), 18 | text: raf.readString(2), 19 | }); 20 | } 21 | // also provide the packet code in hex 22 | result.packetCodeHex = packetCode.toString(16); 23 | 24 | return result; 25 | }; 26 | 27 | module.exports = { 28 | code, 29 | description, 30 | parser, 31 | }; 32 | -------------------------------------------------------------------------------- /src/packets/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | // load all packets in folder automatically 5 | const files = fs.readdirSync(__dirname).filter((file) => !fs.lstatSync(path.join(__dirname, file)).isDirectory() && file !== 'index.js'); 6 | // eslint-disable-next-line import/no-dynamic-require, global-require 7 | const packetsRaw = files.map((file) => require(path.join(__dirname, file))); 8 | 9 | // make up a list of packets by integer type 10 | const packets = {}; 11 | packetsRaw.forEach((packet) => { 12 | if (packets[packet.code]) { throw new Error(`Duplicate packet code ${packet.code}`); } 13 | packets[packet.code] = packet; 14 | }); 15 | 16 | // generic packet parser 17 | const parser = (raf, productDescription) => { 18 | // get the packet code and then jump back in the file so it can be consumed by the packet parser 19 | const packetCode = raf.readUShort(); 20 | raf.skip(-2); 21 | 22 | // turn into hex packet code 23 | const packetCodeHex = packetCode.toString(16).padStart(4, '0'); 24 | 25 | // look up the packet code 26 | const packet = packets[packetCode]; 27 | // first layer always results in an error 28 | if (!packet) throw new Error(`Unsupported packet code 0x${packetCodeHex}`); 29 | return packet.parser(raf, productDescription); 30 | }; 31 | 32 | module.exports = { 33 | packets, 34 | parser, 35 | }; 36 | -------------------------------------------------------------------------------- /src/packets/utilities/ij.js: -------------------------------------------------------------------------------- 1 | // i,j coordinate functions 2 | 3 | // i,j to azimuth/nmi 4 | 5 | // default is 4096 i/j units * 0.125 km, converted to nautical miles = 1km = 0.539957nmi 6 | const ijToAzDeg = (i, j, rawScale = 8, conversion = 0.539957) => { 7 | // calculate nautical miles 8 | const nm = (Math.sqrt(i ** 2 + j ** 2) / rawScale) * conversion; 9 | let deg = 0; 10 | // short circuit potential divide by zero 11 | if (i === 0) { 12 | // calculate degrees, then rotate due to north = up = 0 deg convention 13 | deg = (Math.atan(-j / i) * 180) / Math.PI + 90; 14 | // coerce to 0<=deg<360 15 | if (deg < 0) deg += 180; 16 | } 17 | return { 18 | deg, 19 | nm, 20 | }; 21 | }; 22 | 23 | module.exports = { 24 | ijToAzDeg, 25 | }; 26 | -------------------------------------------------------------------------------- /src/packets/utilities/rle.js: -------------------------------------------------------------------------------- 1 | // run length encoding expansion methods 2 | 3 | // expand rle from rrrrvvvv, 4-bit run, 4-bit value 4 | // eslint-disable-next-line camelcase 5 | const expand4_4 = (byte) => { 6 | const run = byte >> 4; 7 | const value = byte & 0x0F; 8 | const result = []; 9 | for (let i = 0; i < run; i += 1) { 10 | result.push(value); 11 | } 12 | return result; 13 | }; 14 | 15 | module.exports = { 16 | expand4_4, 17 | }; 18 | -------------------------------------------------------------------------------- /src/products/141/formatter.js: -------------------------------------------------------------------------------- 1 | // format the text data provided 2 | // extract data from lines that follow this format 3 | // " U3 0 50 <0.50 " 4 | // using this header information 5 | // " CIRC AZRAN SR STM |-LOW LEVEL-| |--DEPTH--| |-MAX RV-| TVS MOTION MSI " 6 | // " ID deg/nm ID RV DV BASE kft STMREL% kft kts deg/kts " 7 | // returns an array of objects 8 | 9 | module.exports = (data) => { 10 | // extract relevant data 11 | const pages = data?.tabular?.pages; 12 | if (!pages) return {}; 13 | const result = {}; 14 | 15 | // format line by line 16 | pages.forEach((page) => { 17 | page.forEach((line) => { 18 | // extrat values 19 | const rawMatch = line.match(/ +([0-9.]+) +([0-9.]+)\/ *([0-9.]+) +([0-9.]+) +([A-Z0-9]{2}) +([0-9.]+) +([0-9.]+)[ <]+([0-9.]+)[ <>]+([0-9.]+)[ <>]+([0-9.]+)[ <>]+([0-9.]+)[ <>]+([0-9.]+) +([YN]) {1,4}([0-9.]*)\/* {0,3}([0-9.]*) +([0-9.]*)/); 20 | if (!rawMatch) return; 21 | 22 | // format the result 23 | const [, id, az, ran, sr, stmId, llRv, llDv, llBase, depthKft, depthStmrel, maxRvKft, maxrvKts, tvs, motionDeg, motionKts, msi] = [...rawMatch]; 24 | // check for motion 25 | let motion = false; 26 | if (motionDeg !== '') { 27 | motion = { 28 | deg: +motionDeg, 29 | kts: +motionKts, 30 | }; 31 | } 32 | // store to array 33 | result[id] = { 34 | az: +az, 35 | ran: +ran, 36 | sr: +sr, 37 | stmId, 38 | lowLevel: { 39 | rv: +llRv, 40 | dv: +llDv, 41 | base: +llBase, 42 | }, 43 | depth: { 44 | kft: +depthKft, 45 | stmrel: +depthStmrel, 46 | }, 47 | maxRv: { 48 | kft: +maxRvKft, 49 | kts: +maxrvKts, 50 | }, 51 | tvs: tvs === 'Y', 52 | motion, 53 | msi: msi ?? null, 54 | }; 55 | }); 56 | }); 57 | 58 | return { 59 | mesocyclone: result, 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /src/products/141/index.js: -------------------------------------------------------------------------------- 1 | const code = 141; 2 | const abbreviation = ['NMD']; 3 | const description = 'Mesocyclone'; 4 | const formatter = require('./formatter'); 5 | const { RandomAccessFile } = require('../../randomaccessfile'); 6 | 7 | // 124 Nmi, Geographic and Non-geographic alphanumeric 8 | 9 | // eslint-disable-next-line camelcase 10 | const halfwords27_28 = (data) => { 11 | const raf = new RandomAccessFile(data); 12 | return { 13 | minimumReflectivity: raf.readShort(), 14 | overlapDisplayFilter: raf.readShort(), 15 | }; 16 | }; 17 | 18 | // eslint-disable-next-line camelcase 19 | const halfwords30_53 = (data) => { 20 | const raf = new RandomAccessFile(data); 21 | return { 22 | filterStrengthRank: raf.readShort(), 23 | }; 24 | }; 25 | 26 | module.exports = { 27 | code, 28 | abbreviation, 29 | description, 30 | formatter, 31 | productDescription: { 32 | halfwords27_28, 33 | halfwords30_53, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/products/165/index.js: -------------------------------------------------------------------------------- 1 | const code = 165; 2 | const abbreviation = ['N0H', 'N1H', 'N2H', 'N3H']; 3 | const description = 'Hydrometeor Classification'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | const key = { 7 | 0: 'ND: Below Threshold', 8 | 10: 'BI: Biological', 9 | 20: 'GC: Anomalous Propagation/Ground Clutter', 10 | 30: 'IC: Ice Crystals', 11 | 40: 'DS: Dry Snow', 12 | 50: 'WS: Wet Snow', 13 | 60: 'RA: Light and/or Moderate Rain', 14 | 70: 'HR: Heavy Rain', 15 | 80: 'BD: Big Drops (rain)', 16 | 90: 'GR: Graupel', 17 | 100: 'HA: Hail, possibly with rain', 18 | 110: 'LH: Large Hail', 19 | 120: 'GH: Giant Hail', 20 | 140: 'UK: Unknown Classification', 21 | 150: 'RF: Range Folded', 22 | }; 23 | 24 | // eslint-disable-next-line camelcase 25 | const halfwords27_28 = (data) => ({ 26 | halfwords27_28: data, 27 | }); 28 | 29 | // eslint-disable-next-line camelcase 30 | const halfwords30_53 = (data) => { 31 | // turn data into a random access file for bytewise parsing purposes 32 | const raf = new RandomAccessFile(data); 33 | return { 34 | elevationAngle: raf.readShort() / 10, 35 | dependent31_49: raf.read(38), 36 | ...deltaTime(raf.readShort()), 37 | compressionMethod: raf.readShort(), 38 | uncompressedSize: (raf.readUShort() << 16) + raf.readUShort(), 39 | plot: { maxDataValue: 150 }, 40 | }; 41 | }; 42 | 43 | // delta and time are compressed into one field 44 | const deltaTime = (value) => ({ 45 | deltaTime: (value & 0xFFE0) >> 5, 46 | nonSupplementalScan: (value & 0x001F) === 0, 47 | sailsScan: (value & 0x001F) === 1, 48 | mrleScan: (value & 0x001F) === 2, 49 | }); 50 | 51 | module.exports = { 52 | code, 53 | abbreviation, 54 | description, 55 | productDescription: { 56 | halfwords27_28, 57 | halfwords30_53, 58 | }, 59 | supplemental: { key }, 60 | }; 61 | -------------------------------------------------------------------------------- /src/products/170/index.js: -------------------------------------------------------------------------------- 1 | const code = 170; 2 | const abbreviation = 'DAA'; 3 | const description = 'Digital One Hour Accumulation'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | // eslint-disable-next-line camelcase 7 | const halfwords27_28 = (data) => { 8 | // turn data into a random access file for bytewise parsing purposes 9 | const raf = new RandomAccessFile(data); 10 | return { 11 | thresholdMinTime: raf.readShort(), 12 | totalTime: raf.readShort(), 13 | }; 14 | }; 15 | 16 | // eslint-disable-next-line camelcase 17 | const halfwords30_53 = (data) => { 18 | // turn data into a random access file for bytewise parsing purposes 19 | const raf = new RandomAccessFile(data); 20 | return { 21 | nullProductFlag: nullProductFlag(raf.readShort()), 22 | plot: { 23 | scale: raf.readFloat() * 100, 24 | offset: raf.readFloat(), 25 | dependent35: raf.readShort(), 26 | maxDataValue: raf.readShort(), 27 | leadingFlags: leadingFlags(raf.readShort()), 28 | trailingFlags: raf.readShort(), 29 | }, 30 | dependent39_46: raf.read(16), 31 | maxAccumulation: raf.readShort() / 10, 32 | accumulationEndDate: raf.readShort(), 33 | accumulationEndMinutes: raf.readShort(), 34 | meanFieldBias: raf.readShort() / 1000, 35 | compressionMethod: raf.readShort(), 36 | uncompressedSize: (raf.readUShort() << 16) + raf.readUShort(), 37 | }; 38 | }; 39 | 40 | const nullProductFlag = (data) => { 41 | if (data === 0) return false; 42 | let reason = ''; 43 | switch (data) { 44 | case 0: 45 | reason = false; 46 | break; 47 | case 1: 48 | reason = 'No accumulation available. Threshold: ‘Elapsed Time to Restart’ [TIMRS] xx minutes exceeded.'; 49 | break; 50 | case 2: 51 | reason = 'No precipitation detected during the specified time span.'; 52 | break; 53 | case 3: 54 | reason = 'No accumulation data available for the specified time span.'; 55 | break; 56 | case 4: 57 | reason = 'No precipitation detected since hh:mmZ. Threshold: \'Time Without Precipitation for Resetting Storm Totals\' [RAINT] is xx minutes or No precipitation detected since RPG startup.'; 58 | break; 59 | case 5: 60 | reason = 'No precipitation detected since hh:mmZ or No precipitation detected since RPG startup.'; 61 | break; 62 | case 6: 63 | reason = 'No Top_of_Hour accumulation - Some problem encountered with the SQL query resulted in an error.'; 64 | break; 65 | case 7: 66 | reason = 'No Top_of_Hour accumulation because of excessive missing time encountered.'; 67 | break; 68 | default: 69 | reason = 'Undefined'; 70 | } 71 | return { 72 | value: data, 73 | reason, 74 | }; 75 | }; 76 | 77 | const leadingFlags = (data) => ({ 78 | noData: data & 0x01 === 0, 79 | }); 80 | 81 | module.exports = { 82 | code, 83 | abbreviation, 84 | description, 85 | productDescription: { 86 | halfwords27_28, 87 | halfwords30_53, 88 | }, 89 | }; 90 | -------------------------------------------------------------------------------- /src/products/172/index.js: -------------------------------------------------------------------------------- 1 | const code = 172; 2 | const abbreviation = 'DTA'; 3 | const description = 'Storm Total Precipitation'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | // eslint-disable-next-line camelcase 7 | const halfwords27_28 = (data) => { 8 | // turn data into a random access file for bytewise parsing purposes 9 | const raf = new RandomAccessFile(data); 10 | return { 11 | accumulationStartDate: raf.readShort(), 12 | accumulationStartMinutes: raf.readShort(), 13 | }; 14 | }; 15 | 16 | // eslint-disable-next-line camelcase 17 | const halfwords30_53 = (data) => { 18 | // turn data into a random access file for bytewise parsing purposes 19 | const raf = new RandomAccessFile(data); 20 | return { 21 | nullProductFlag: nullProductFlag(raf.readShort()), 22 | plot: { 23 | scale: raf.readFloat() * 100, 24 | offset: raf.readFloat(), 25 | dependent35: raf.readShort(), 26 | maxDataValue: raf.readShort(), 27 | leadingFlags: leadingFlags(raf.readShort()), 28 | trailingFlags: raf.readShort(), 29 | }, 30 | dependent39_46: raf.read(16), 31 | maxAccumulation: raf.readShort() / 10, 32 | accumulationEndDate: raf.readShort(), 33 | accumulationEndMinutes: raf.readShort(), 34 | meanFieldBias: raf.readShort() / 1000, 35 | compressionMethod: raf.readShort(), 36 | uncompressedSize: (raf.readUShort() << 16) + raf.readUShort(), 37 | }; 38 | }; 39 | 40 | const nullProductFlag = (data) => { 41 | if (data === 0) return false; 42 | let reason = ''; 43 | switch (data) { 44 | case 0: 45 | reason = false; 46 | break; 47 | case 1: 48 | reason = 'No accumulation available. Threshold: ‘Elapsed Time to Restart’ [TIMRS] xx minutes exceeded.'; 49 | break; 50 | case 2: 51 | reason = 'No precipitation detected during the specified time span.'; 52 | break; 53 | case 3: 54 | reason = 'No accumulation data available for the specified time span.'; 55 | break; 56 | case 4: 57 | reason = 'No precipitation detected since hh:mmZ. Threshold: \'Time Without Precipitation for Resetting Storm Totals\' [RAINT] is xx minutes or No precipitation detected since RPG startup.'; 58 | break; 59 | case 5: 60 | reason = 'No precipitation detected since hh:mmZ or No precipitation detected since RPG startup.'; 61 | break; 62 | case 6: 63 | reason = 'No Top_of_Hour accumulation - Some problem encountered with the SQL query resulted in an error.'; 64 | break; 65 | case 7: 66 | reason = 'No Top_of_Hour accumulation because of excessive missing time encountered.'; 67 | break; 68 | default: 69 | reason = 'Undefined'; 70 | } 71 | return { 72 | value: data, 73 | reason, 74 | }; 75 | }; 76 | 77 | const leadingFlags = (data) => ({ 78 | noData: data & 0x01 === 0, 79 | }); 80 | 81 | module.exports = { 82 | code, 83 | abbreviation, 84 | description, 85 | productDescription: { 86 | halfwords27_28, 87 | halfwords30_53, 88 | }, 89 | }; 90 | -------------------------------------------------------------------------------- /src/products/177/index.js: -------------------------------------------------------------------------------- 1 | const code = 177; 2 | const abbreviation = 'HHC'; 3 | const description = 'Hybrid Hydrometeor Classification'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | // uses the same data coding as 165 7 | const { key } = require('../165').supplemental; 8 | 9 | // eslint-disable-next-line camelcase 10 | const halfwords27_28 = (data) => ({ 11 | halfwords27_28: data, 12 | }); 13 | 14 | // eslint-disable-next-line camelcase 15 | const halfwords30_53 = (data) => { 16 | // turn data into a random access file for bytewise parsing purposes 17 | const raf = new RandomAccessFile(data); 18 | return { 19 | dependent30_46: raf.read(34), 20 | modeFilter: raf.readShort(), 21 | hybridRatePercentBinsFilled: raf.readShort() / 100, 22 | highestElevation: raf.readShort() / 10, 23 | dependent50: raf.read(2), 24 | compressionMethod: raf.readShort(), 25 | uncompressedSize: (raf.readUShort() << 16) + raf.readUShort(), 26 | plot: { maxDataValue: 150 }, 27 | }; 28 | }; 29 | 30 | module.exports = { 31 | code, 32 | abbreviation, 33 | description, 34 | productDescription: { 35 | halfwords27_28, 36 | halfwords30_53, 37 | }, 38 | supplemental: { key }, 39 | }; 40 | -------------------------------------------------------------------------------- /src/products/56/index.js: -------------------------------------------------------------------------------- 1 | const code = 56; 2 | const abbreviation = ['N0S', 'N1S', 'N2S', 'N3S']; 3 | const description = 'Storm relative velocity'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | // eslint-disable-next-line camelcase 7 | const halfwords30_53 = (data) => { 8 | // turn data into a random access file for bytewise parsing purposes 9 | const raf = new RandomAccessFile(data); 10 | return { 11 | elevationAngle: raf.readShort() / 10, 12 | dependent31_46: raf.read(32), 13 | maxNegativeVelocity: raf.readShort(), // knots 14 | maxPositiveVelocity: raf.readShort(), // knots 15 | motionSourceFlag: raf.readShort(), // = -1 16 | dependent50: raf.readShort(), 17 | averageStormSpeed: raf.readShort() / 10, // knots 18 | averageStormDirection: raf.readShort() / 10, // degrees 19 | }; 20 | }; 21 | 22 | module.exports = { 23 | code, 24 | abbreviation, 25 | description, 26 | 27 | productDescription: { 28 | halfwords30_53, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/products/58/formatter.js: -------------------------------------------------------------------------------- 1 | // format the text data provided 2 | // extract data from lines that follow this format 3 | // " P2 244/125 232/ 38 245/116 246/107 247/ 97 NO DATA 1.1/ 0.9" 4 | // using this header information 5 | // " STORM CURRENT POSITION FORECAST POSITIONS ERROR ", 6 | // " ID AZRAN MOVEMENT 15 MIN 30 MIN 45 MIN 60 MIN FCST/MEAN", 7 | // " (DEG/NM) (DEG/KTS) (DEG/NM) (DEG/NM) (DEG/NM) (DEG/NM) (NM) " 8 | // returns an an arrya of objects { 9 | // id: id of storm assigned by algorithm 10 | // current: {deg,nm} current position from radar in degrees and nautical miles 11 | // movement: {deg,kts} movement of storm in degrees and knots 12 | // forecast: [{deg, nm},...] forecasted position of storm in degrees and nm from radar site at [15,30,45,60] minutes 13 | // } 14 | 15 | module.exports = (data) => { 16 | // extract relevant data 17 | const pages = data?.tabular?.pages; 18 | if (!pages) return {}; 19 | const result = {}; 20 | 21 | // format line by line 22 | pages.forEach((page) => { 23 | page.forEach((line) => { 24 | // look for ID and current position to find valid line 25 | const idMatch = line.match(/ {2}([A-Z][0-9]) {5}[0-9 ]{3}\/[0-9 ]{3} {3}/); 26 | if (!idMatch) return; 27 | 28 | // store the id 29 | const id = idMatch[1]; 30 | 31 | // extract 6 positional values 32 | const rawPositions = [...line.matchAll(/([ 0-9]{3}\/[ 0-9]{3}|NO DATA| {2}NEW {2})/g)]; 33 | // extract the matched strings and parse into objects 34 | // second string (index 1) is in knots 35 | const stringPositions = rawPositions.map((position, index) => parseStringPosition(position[1], index === 1)); 36 | 37 | // format the result 38 | const [current, movement, ...forecast] = stringPositions; 39 | // store to array 40 | result[id] = { 41 | current, movement, forecast, 42 | }; 43 | }); 44 | }); 45 | 46 | return { 47 | storms: result, 48 | }; 49 | }; 50 | 51 | // parse no data, new and positional info 52 | // kts returns {deg,kts} instead of the default {deg,nm} 53 | const parseStringPosition = (position, kts = false) => { 54 | // fixed strings 55 | if (position === 'NO DATA') return null; 56 | if (position === ' NEW ') return 'new'; 57 | 58 | // extract the two numbers 59 | const values = position.match(/([ 0-9]{3})\/([ 0-9]{3})/); 60 | // couldn't find two numbers 61 | if (!values) return undefined; 62 | // return the formatted numbers 63 | if (kts) { 64 | return { 65 | deg: +values[1], 66 | kts: +values[2], 67 | }; 68 | } 69 | return { 70 | deg: +values[1], 71 | nm: +values[2], 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /src/products/58/index.js: -------------------------------------------------------------------------------- 1 | const code = 58; 2 | const abbreviation = ['NST']; 3 | const description = 'Storm Tracking Information'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | const formatter = require('./formatter'); 6 | 7 | // 248 Nmi, Geographic and Non-geographic alphanumeric 8 | 9 | // eslint-disable-next-line camelcase 10 | const halfwords30_53 = (data) => { 11 | // turn data into a random access file for bytewise parsing purposes 12 | const raf = new RandomAccessFile(data); 13 | return { 14 | elevationAngle: raf.readShort() / 10, 15 | dependent31_46: raf.read(32), 16 | maxNegativeVelocity: raf.readShort(), // knots 17 | maxPositiveVelocity: raf.readShort(), // knots 18 | motionSourceFlag: raf.readShort(), // = -1 19 | dependent50: raf.readShort(), 20 | averageStormSpeed: raf.readShort() / 10, // knots 21 | averageStormDirection: raf.readShort() / 10, // degrees 22 | }; 23 | }; 24 | 25 | module.exports = { 26 | code, 27 | abbreviation, 28 | description, 29 | formatter, 30 | 31 | productDescription: { 32 | halfwords30_53, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/products/59/formatter.js: -------------------------------------------------------------------------------- 1 | // format the text data provided 2 | // extract data from lines that follow this format 3 | // " U3 0 50 <0.50 " 4 | // using this header information 5 | // " STORM PROBABILITY OF PROBABILITY OF MAX EXPECTED " 6 | // " ID SEVERE HAIL (%) HAIL (%) HAIL SIZE (IN) " 7 | // returns an array of objects { 8 | // id: id of storm assigned by algorithm 9 | // probSevere: probability of severe hail % 10 | // probHail: probability of hail % 11 | // maxSize: max expected size of hail (read as { 15 | // extract relevant data 16 | const pages = data?.tabular?.pages; 17 | if (!pages) return {}; 18 | const result = {}; 19 | 20 | // format line by line 21 | pages.forEach((page) => { 22 | page.forEach((line) => { 23 | // extrat values 24 | const rawMatch = line.match(/ {8}([A-Z]\d) {4} *([0-9.]{1,3}) *([0-9.]{1,3}) *?([0-9.]{4,6}) */); 25 | if (!rawMatch) return; 26 | 27 | // format the result 28 | const [, id, probSevere, probHail, maxSize] = [...rawMatch]; 29 | // store to array 30 | result[id] = { 31 | probSevere: +probSevere, 32 | probHail: +probHail, 33 | maxSize: +maxSize, 34 | }; 35 | }); 36 | }); 37 | 38 | return { 39 | hail: result, 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /src/products/59/index.js: -------------------------------------------------------------------------------- 1 | const code = 59; 2 | const abbreviation = ['NHI']; 3 | const description = 'Hail Index'; 4 | const formatter = require('./formatter'); 5 | 6 | // 124 Nmi, Geographic and Non-geographic alphanumeric 7 | 8 | module.exports = { 9 | code, 10 | abbreviation, 11 | description, 12 | formatter, 13 | 14 | productDescription: { 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/products/61/formatter.js: -------------------------------------------------------------------------------- 1 | // format the text data provided 2 | // extract data from lines that follow this format 3 | // " TVS F0 74/ 52 35 52 52/ 4.9 >11.1 < 4.9/ 16.0 16/ 4.9 " 4 | // using this header information 5 | // " Feat Storm AZ/RAN AVGDV LLDV MXDV/Hgt Depth Base/Top MXSHR/Hgt " 6 | // " Type ID (deg,nm) (kt) (kt) (kt,kft) (kft) (kft) (E-3/s,kft) " 7 | // returns an array of objects { 8 | // type: feature type 9 | // id: id of storm assigned by algorithm 10 | // az: azimuth 11 | // range: range to storm 12 | // avgdv 13 | // lldv 14 | // mxdv 15 | // mxdvhgt 16 | // depth 17 | // base 18 | // top 19 | // maxshear 20 | // maxshearheight 21 | // } 22 | 23 | module.exports = (data) => { 24 | // extract relevant data 25 | const pages = data?.tabular?.pages; 26 | if (!pages) return {}; 27 | const result = {}; 28 | 29 | // format line by line 30 | pages.forEach((page) => { 31 | page.forEach((line) => { 32 | // extrat values 33 | const rawMatch = line.match(/ {2}([A-Z0-9]{3}) {4}([A-Z][0-9]) {3,5}([0-9.]{1,3})\/ {0,2}([0-9.]{1,3}) {3,5}([0-9.]{1,3}) {3,5}([0-9.]{1,3}) {3,5}([0-9.]{1,3})\/ {0,2}([0-9.]{1,3})[ <>]{4}([0-9.]{4})[ <>]{3,4}([0-9.]{3,4})\/ {0,2}([0-9.]{1,4}) {3,5}([0-9.]{2,4})\/ {0,2}([0-9.]{1,4})/); 34 | if (!rawMatch) return; 35 | 36 | // format the result 37 | const [, type, id, az, range, avfdv, lldv, mxdv, mvdvhgt, depth, base, top, maxshear, maxshearheight] = [...rawMatch]; 38 | // store to array 39 | result[id] = { 40 | type, 41 | az: +az, 42 | range: +range, 43 | avfdv: +avfdv, 44 | lldv: +lldv, 45 | mxdv: +mxdv, 46 | mvdvhgt: +mvdvhgt, 47 | depth: +depth, 48 | base: +base, 49 | top: +top, 50 | maxshear: +maxshear, 51 | maxshearheight: +maxshearheight, 52 | }; 53 | }); 54 | }); 55 | 56 | return { 57 | tvs: result, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/products/61/index.js: -------------------------------------------------------------------------------- 1 | const code = 61; 2 | const abbreviation = ['NTV']; 3 | const description = 'Tornadic Vortex Signature'; 4 | const formatter = require('./formatter'); 5 | 6 | module.exports = { 7 | code, 8 | abbreviation, 9 | description, 10 | formatter, 11 | 12 | productDescription: { 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/products/62/index.js: -------------------------------------------------------------------------------- 1 | const code = 62; 2 | const abbreviation = ['NSS']; 3 | const description = 'Storm Structure'; 4 | 5 | // not much work to do for this product 6 | // the data resides in the headers directly 7 | // see symbology6.js and graphic22.js 8 | 9 | module.exports = { 10 | code, 11 | abbreviation, 12 | description, 13 | 14 | productDescription: { 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/products/78/index.js: -------------------------------------------------------------------------------- 1 | const code = 78; 2 | const abbreviation = 'N1P'; 3 | const description = 'One-hour precipitation'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | // eslint-disable-next-line camelcase 7 | const halfwords30_53 = (data) => { 8 | // turn data into a random access file for bytewise parsing purposes 9 | const raf = new RandomAccessFile(data); 10 | raf.seek(34); 11 | return { 12 | maxRainfall: raf.readShort() / 10, 13 | meanFieldBias: raf.readShort() / 100, 14 | sampleSize: raf.readShort() / 100, 15 | endRanifallDate: raf.readShort(), 16 | endRainfallMinutes: raf.readShort(), 17 | plot: { 18 | maxDataValue: 16, 19 | }, 20 | }; 21 | }; 22 | 23 | module.exports = { 24 | code, 25 | abbreviation, 26 | description, 27 | productDescription: { 28 | halfwords30_53, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/products/80/index.js: -------------------------------------------------------------------------------- 1 | const code = 80; 2 | const abbreviation = 'NTP'; 3 | const description = 'Storm Total Rainfall Accumulation'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | // eslint-disable-next-line camelcase 7 | const halfwords30_53 = (data) => { 8 | // turn data into a random access file for bytewise parsing purposes 9 | const raf = new RandomAccessFile(data); 10 | raf.seek(34); 11 | return { 12 | maxRainfall: raf.readShort() / 10, 13 | beginRanifallDate: raf.readShort(), 14 | beginRainfallMinutes: raf.readShort(), 15 | endRanifallDate: raf.readShort(), 16 | endRainfallMinutes: raf.readShort(), 17 | meanFieldBias: raf.readShort() / 100, 18 | sampleSize: raf.readShort() / 100, 19 | plot: { 20 | maxDataValue: 16, 21 | }, 22 | }; 23 | }; 24 | 25 | module.exports = { 26 | code, 27 | abbreviation, 28 | description, 29 | productDescription: { 30 | halfwords30_53, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/products/94/index.js: -------------------------------------------------------------------------------- 1 | const code = 94; 2 | const abbreviation = ['NXQ', 'NYQ', 'NZQ', 'N0Q', 'NAQ', 'N1Q', 'NBQ', 'N2Q', 'N3Q']; 3 | const description = 'Digital Base Reflectivity'; 4 | const { RandomAccessFile } = require('../../randomaccessfile'); 5 | 6 | // eslint-disable-next-line camelcase 7 | const halfwords30_53 = (data) => { 8 | // turn data into a random access file for bytewise parsing purposes 9 | const raf = new RandomAccessFile(data); 10 | return { 11 | elevationAngle: raf.readShort() / 10, 12 | plot: { 13 | minimumDataValue: raf.readShort() / 10, 14 | dataIncrement: raf.readShort() / 10, 15 | dataLevels: raf.readShort(), 16 | }, 17 | dependent34_46: raf.read(26), 18 | maxReflectivity: raf.readShort(), // dBZ 19 | dependent48_49: raf.read(4), 20 | ...deltaTime(raf.readShort()), 21 | compressionMethod: raf.readShort(), 22 | uncompressedProductSize: (raf.readUShort() << 16) + raf.readUShort(), 23 | }; 24 | }; 25 | 26 | // delta and time are compressed into one field 27 | const deltaTime = (value) => ({ 28 | deltaTime: (value & 0xFFE0) >> 5, 29 | nonSupplementalScan: (value & 0x001F) === 0, 30 | sailsScan: (value & 0x001F) === 1, 31 | mrleScan: (value & 0x001F) === 2, 32 | }); 33 | 34 | module.exports = { 35 | code, 36 | abbreviation, 37 | description, 38 | 39 | productDescription: { 40 | halfwords30_53, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/products/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | // load all products in folder automatically 5 | const folders = fs.readdirSync(__dirname).filter((folder) => fs.lstatSync(path.join(__dirname, folder)).isDirectory()); 6 | // eslint-disable-next-line import/no-dynamic-require, global-require 7 | const productsRaw = folders.map((folder) => require(path.join(__dirname, folder))); 8 | 9 | // make up a list of products by integer type 10 | const products = {}; 11 | productsRaw.forEach((product) => { 12 | if (products[product.code]) { throw new Error(`Duplicate product code ${product.code}`); } 13 | products[product.code] = product; 14 | }); 15 | 16 | // list of available product code abbreviations for type-checking 17 | const productAbbreviations = productsRaw.map((product) => product.abbreviation).flat(); 18 | 19 | module.exports = { 20 | products, 21 | productAbbreviations, 22 | }; 23 | -------------------------------------------------------------------------------- /src/randomaccessfile/index.js: -------------------------------------------------------------------------------- 1 | const BIG_ENDIAN = 0; 2 | const LITTLE_ENDIAN = 1; 3 | 4 | // store a buffer or string and add functionality for random access 5 | class RandomAccessFile { 6 | constructor(file, endian = BIG_ENDIAN, stringFormat = 'utf-8') { 7 | this.offset = 0; 8 | this.buffer = null; 9 | this.stringFormat = stringFormat; 10 | 11 | // set the binary endian order 12 | if (endian < 0) return; 13 | this.bigEndian = (endian === BIG_ENDIAN); 14 | 15 | // string to buffer if string was provided 16 | if (typeof file === 'string') { 17 | this.buffer = Buffer.from(file, 'binary'); 18 | } else { 19 | // load the buffer directly 20 | this.buffer = file; 21 | } 22 | 23 | // set up local read functions so we don't constantly query endianess 24 | if (this.bigEndian) { 25 | this.readFloatLocal = this.buffer.readFloatBE.bind(this.buffer); 26 | this.readIntLocal = this.buffer.readIntBE.bind(this.buffer); 27 | this.readUIntLocal = this.buffer.readUIntBE.bind(this.buffer); 28 | } else { 29 | this.readFloatLocal = this.buffer.readFloatLE.bind(this.buffer); 30 | this.readIntLocal = this.buffer.readIntLE.bind(this.buffer); 31 | this.readUIntLocal = this.buffer.readUIntLE.bind(this.buffer); 32 | } 33 | } 34 | 35 | // return the current buffer length 36 | getLength() { 37 | return this.buffer.length; 38 | } 39 | 40 | // return the current position in the file 41 | getPos() { 42 | return this.offset; 43 | } 44 | 45 | // seek to a provided buffer offset 46 | seek(byte) { 47 | this.offset = byte; 48 | } 49 | 50 | // read a string from the buffer 51 | readString(bytes) { 52 | const data = this.buffer.toString(this.stringFormat, this.offset, (this.offset += bytes)); 53 | return data; 54 | } 55 | 56 | // read a float from the buffer 57 | readFloat() { 58 | const float = this.readFloatLocal(this.offset); 59 | this.offset += 4; 60 | return float; 61 | } 62 | 63 | // read a number from the buffer 64 | readInt() { 65 | const int = this.readIntLocal(this.offset, 4); 66 | this.offset += 4; 67 | return int; 68 | } 69 | 70 | // read an unsigned number from the buffer 71 | readUInt() { 72 | const int = this.readUIntLocal(this.offset, 4); 73 | this.offset += 4; 74 | return int; 75 | } 76 | 77 | // read a short from the buffer 78 | readShort() { 79 | const short = this.readIntLocal(this.offset, 2); 80 | this.offset += 2; 81 | return short; 82 | } 83 | 84 | // read an unsigned short from the buffer 85 | readUShort() { 86 | const short = this.readUIntLocal(this.offset, 2); 87 | this.offset += 2; 88 | return short; 89 | } 90 | 91 | // read a byte from the buffer 92 | readByte() { 93 | return this.read()[0]; 94 | } 95 | 96 | // read a set number of bytes from the buffer and return as an array 97 | read(bytes = 1) { 98 | const data = this.buffer.slice(this.offset, this.offset + bytes); 99 | this.offset += bytes; 100 | return data; 101 | } 102 | 103 | // skip a set number of bites and update the offset 104 | skip(bytes) { 105 | this.offset += bytes; 106 | } 107 | } 108 | 109 | module.exports.RandomAccessFile = RandomAccessFile; 110 | module.exports.BIG_ENDIAN = BIG_ENDIAN; 111 | module.exports.LITTLE_ENDIAN = LITTLE_ENDIAN; 112 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable no-unused-vars */ 3 | 4 | const fs = require('fs'); 5 | const util = require('util'); 6 | const parser = require('./src'); 7 | 8 | // read file 9 | 10 | // 56 N0S Storm relative velocity 11 | // const file = fs.readFileSync('./data/LOT_N0S_2021_01_31_11_06_30'); 12 | 13 | // 58 NTP Storm Tracking Information 14 | // const file = fs.readFileSync('./data/JAX_NST_2021_04_11_19_37_00'); 15 | // const file = fs.readFileSync('./data/TBW_NST_2021_04_19_19_02'); 16 | // const file = fs.readFileSync('./data/KDGX_NST_2022_01_16_09_31'); // error packet id 0x0019 17 | 18 | // 59 NHI Hail Index 19 | // const file = fs.readFileSync('./data/DTW_NHI_2021_04_08_19_47'); 20 | // const file = fs.readFileSync('./data/TBW_NHI_2021_04_19_19_02'); 21 | 22 | // 61 NTV 23 | // const file = fs.readFileSync('./data/sn.0011'); 24 | // const file = fs.readFileSync('./data/sn.0012'); 25 | 26 | // 62 NSS 27 | // const file = fs.readFileSync('./data/TBW_NSS_2021_04_19_19_02'); 28 | // const file = fs.readFileSync('./data/KBYX_NSS_2022_01_16_09_31'); // error with block id 4 29 | // const file = fs.readFileSync('./data/KCAE_NSS_2022_01_16_09_31'); // error with block id 5 30 | // const file = fs.readFileSync('./data/KFFC_NSS_2022_01_16_09_31'); // error with block id 7 31 | // const file = fs.readFileSync('./data-error/KLOT_NSS_2022_01_16_09_31'); // error with block id 3 32 | const file = fs.readFileSync('./data-error/KLTX_NSS_2022_02_15_06_44'); // error with block id 3 33 | 34 | // 78 One-hour precipitation 35 | // const file = fs.readFileSync('./data/LOT_N1P_2021_01_31_11_06_30'); 36 | 37 | // 80 NTP Storm total accumulation 38 | // const file = fs.readFileSync('./data/LOT_NTP_2021_01_31_11_06_30'); 39 | 40 | // 94 NXQ Digital Base Reflectivy 41 | // const file = fs.readFileSync('./data/SDUS53 KLOT 150709'); 42 | 43 | // 141 NMD Mesocyclone 44 | // const file = fs.readFileSync('./data/LOT_NMD_2021_06_21_04_22_17'); 45 | 46 | // 165 N0H Hydrometeor classification 47 | // const file = fs.readFileSync('./data/LOT_N0H_2021_01_31_11_06_30'); 48 | 49 | // 172 DTA Storm Total Precipitation 50 | // const file = fs.readFileSync('./data/LOT_DTA_2021_02_28_15_05_33'); 51 | // const file = fs.readFileSync('./data/LOT_DTA_2021_05_08_03_47_25'); // has error 52 | // const file = fs.readFileSync('./data/LOT_DAA_2021_05_08_03_40_29'); // different radial packet from standard = 1, no radial data 53 | 54 | // 177 HHC Hybrid Hydrometeor classification 55 | // const file = fs.readFileSync('./data/LOT_HHC_2021_01_31_11_06_30'); 56 | 57 | // custom logger for testing 58 | const customLogger = { 59 | log: (message) => { console.log('CUSTOM LOG MESSAGE'); console.log(message); }, 60 | error: (message) => { console.log('CUSTOM ERROR MESSAGE'); console.error(message); }, 61 | }; 62 | 63 | // pass to parser as a string or buffer 64 | const level3Data = parser(file); 65 | 66 | // try different loggers 67 | // const level3Data = parser(file, { logger: false }); 68 | // const level3Data = parser(file, { logger: customLogger }); 69 | 70 | // console.log(util.inspect(level3Data, true, 10)); 71 | console.log(level3Data); 72 | console.log(); 73 | -------------------------------------------------------------------------------- /testoutput.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const fs = require('fs'); 3 | const parser = require('./src'); 4 | 5 | // get a list of all data files 6 | const files = fs.readdirSync('./data/'); 7 | 8 | // test each file 9 | console.log('Testing files without errors'); 10 | files.forEach((file) => { 11 | console.log(file); 12 | try { 13 | const rawFile = fs.readFileSync(`./data/${file}`); 14 | const data = parser(rawFile); 15 | console.log(data); 16 | // write to disk 17 | fs.writeFileSync(`./output/${file}.json`, JSON.stringify(data, null, 2)); 18 | } catch (e) { 19 | console.error(e.stack); 20 | } 21 | }); 22 | 23 | // test each file with known errors 24 | console.log('Testing files with errors'); 25 | const errors = fs.readdirSync('./data-error/'); 26 | errors.forEach((file) => { 27 | console.log(file); 28 | try { 29 | const rawFile = fs.readFileSync(`./data-error/${file}`); 30 | const data = parser(rawFile); 31 | console.log(data); 32 | // write to disk 33 | fs.writeFileSync(`./output/${file}.json`, JSON.stringify(data, null, 2)); 34 | } catch (e) { 35 | console.error(e.stack); 36 | } 37 | }); 38 | 39 | console.log(); 40 | -------------------------------------------------------------------------------- /workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.exclude": { 9 | "node_modules": true 10 | }, 11 | "files.eol": "\n", 12 | "cSpell.words": [ 13 | "bzip", 14 | "ddhhmm", 15 | "mesocyclone", 16 | "mrle", 17 | "mseconds", 18 | "rrrrvvvv", 19 | "SDUS", 20 | "Symbologies" 21 | ], 22 | "cSpell.ignorePaths": [ 23 | "**/package-lock.json", 24 | "**/node_modules/**", 25 | "**/vscode-extension/**", 26 | "**/.git/objects/**", 27 | ".vscode", 28 | ".vscode-insiders", 29 | "data", 30 | "output", 31 | ], 32 | "search.exclude": { 33 | "**/data": true, 34 | "**/output": true 35 | }, 36 | "editor.formatOnSave": true, 37 | } 38 | } --------------------------------------------------------------------------------