diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index defb13d..45ff75a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,9 @@ jobs: - name: Test run: npm test + - name: Coverage + run: npm run coverage + - name: Typecheck run: npm run typecheck diff --git a/.gitignore b/.gitignore index f06235c..0e75fe5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules dist +coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 659532d..c86927b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Code coverage check (thanks [@simmo]) +- Microseconds and nanoseconds support (thanks [@simmo]) + ## [1.1.2] - 2025-05-06 ### Changes @@ -84,5 +89,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [@simmo]: https://github.com/simmo [@spyros-uk]: https://github.com/spyros-uk [1.1.1]: https://github.com/simmo/niobe/compare/1.1.1-beta.2...1.1.1 - [1.1.2]: https://github.com/simmo/niobe/compare/1.1.2-beta.0...1.1.2 diff --git a/package-lock.json b/package-lock.json index 2404e8f..75d7ac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@rollup/plugin-typescript": "^12.1.2", "@tsconfig/node-lts": "^22.0.1", "@types/node": "^22.15.3", + "@vitest/coverage-v8": "^3.1.3", "prettier": "^3.5.3", "release-it": "^19.0.2", "rollup": "^4.40.1", @@ -26,6 +27,20 @@ "vitest": "^3.1.3" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -42,17 +57,66 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", @@ -796,6 +860,90 @@ } } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -1084,6 +1232,17 @@ "url": "https://github.com/phun-ky/typeof?sponsor=1" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@release-it/keep-a-changelog": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@release-it/keep-a-changelog/-/keep-a-changelog-7.0.0.tgz", @@ -1587,6 +1746,39 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/coverage-v8": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.3.tgz", + "integrity": "sha512-cj76U5gXCl3g88KSnf80kof6+6w+K4BjOflCl7t6yRJPDuCrHtVu0SgNYOUARJOL5TI8RScDbm5x4s1/P9bvpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.1.3", + "vitest": "3.1.3" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", @@ -1801,6 +1993,13 @@ "retry": "0.13.1" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/basic-ftp": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", @@ -1818,6 +2017,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2220,6 +2429,13 @@ "url": "https://dotenvx.com" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -2458,6 +2674,23 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2576,6 +2809,37 @@ "git-up": "^8.1.0" } }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2589,6 +2853,13 @@ "node": ">= 0.4" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -2848,6 +3119,76 @@ "node": "^18.17 || >=20.6.1" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -3000,6 +3341,34 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3056,6 +3425,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3314,6 +3709,13 @@ "node": ">= 14" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse-path": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", @@ -3355,6 +3757,30 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -3968,6 +4394,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -3984,6 +4456,30 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -3997,6 +4493,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -4029,6 +4538,21 @@ "node": ">=10" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -4431,6 +4955,70 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", diff --git a/package.json b/package.json index 09daecc..c81b43d 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "prebuild": "rm -rf ./dist", "postbuild": "rm -rf ./dist/.types", "build": "rollup -c rollup.config.ts --configPlugin typescript", + "coverage": "vitest run --coverage", "format": "prettier --check .", "format:fix": "prettier --write .", "release": "release-it", @@ -50,6 +51,7 @@ "@rollup/plugin-typescript": "^12.1.2", "@tsconfig/node-lts": "^22.0.1", "@types/node": "^22.15.3", + "@vitest/coverage-v8": "^3.1.3", "prettier": "^3.5.3", "release-it": "^19.0.2", "rollup": "^4.40.1", diff --git a/src/constant.test.ts b/src/constant.test.ts index 57228c8..01ea857 100644 --- a/src/constant.test.ts +++ b/src/constant.test.ts @@ -9,17 +9,39 @@ const constantsAndValues: [ ['DAYS_IN_WEEK', 7], ['HOUR', 3_600_000], ['HOURS_IN_DAY', 24], + ['MICROSECOND', 0.001], + ['MICROSECONDS_IN_A_MILLISECOND', 1_000], ['MILLISECOND', 1], ['MILLISECONDS_IN_A_SECOND', 1_000], ['MINUTE', 60_000], ['MINUTES_IN_HOUR', 60], + ['NANOSECOND', 0.000_001], + ['NANOSECONDS_IN_A_MICROSECOND', 1_000], ['SECOND', 1_000], ['SECONDS_IN_MINUTE', 60], ['WEEK', 604_800_000], ]; -describe.each(constantsAndValues)('%s', (constant, value) => { - it(`returns ${value}`, _ => { - expect(constants[constant]).toBe(value); +describe('constants', () => { + it('exports the correct values', () => { + expect(constants).toMatchInlineSnapshot(` + { + "DAY": 86400000, + "DAYS_IN_WEEK": 7, + "HOUR": 3600000, + "HOURS_IN_DAY": 24, + "MICROSECOND": 0.001, + "MICROSECONDS_IN_A_MILLISECOND": 1000, + "MILLISECOND": 1, + "MILLISECONDS_IN_A_SECOND": 1000, + "MINUTE": 60000, + "MINUTES_IN_HOUR": 60, + "NANOSECOND": 0.000001, + "NANOSECONDS_IN_A_MICROSECOND": 1000, + "SECOND": 1000, + "SECONDS_IN_MINUTE": 60, + "WEEK": 604800000, + } + `); }); }); diff --git a/src/constant.ts b/src/constant.ts index ec9fb76..1109f04 100644 --- a/src/constant.ts +++ b/src/constant.ts @@ -1,3 +1,15 @@ +/** + * One nanosecond in milliseconds. + */ + +export const NANOSECOND = 0.000_001; + +/** + * One microsecond in milliseconds. + */ + +export const MICROSECOND = 0.001; + /** * One unit of milliseconds. */ @@ -34,6 +46,18 @@ export const DAY = 86_400_000; export const WEEK = 604_800_000; +/** + * Number of nanoseconds in a microsecond. + */ + +export const NANOSECONDS_IN_A_MICROSECOND = 1_000; + +/** + * Number of microseconds in a millisecond. + */ + +export const MICROSECONDS_IN_A_MILLISECOND = 1_000; + /** * Number of milliseconds in a second. */ diff --git a/src/conversion.test.ts b/src/conversion.test.ts index b5323e1..53ff362 100644 --- a/src/conversion.test.ts +++ b/src/conversion.test.ts @@ -1,8 +1,20 @@ import { describe, expect, it } from 'vitest'; -import { days, hours, minutes, seconds, weeks } from './conversion.js'; +import { + days, + hours, + microseconds, + milliseconds, + minutes, + nanoseconds, + seconds, + weeks, +} from './conversion.js'; import { UnitConverter } from './utils/createUnitConverter.js'; const testCases: [unit: string, fn: UnitConverter, milliseconds: number][] = [ + ['nanoseconds', nanoseconds, 0.000_001], + ['microseconds', microseconds, 0.001], + ['milliseconds', milliseconds, 1], ['seconds', seconds, 1_000], ['minutes', minutes, 60_000], ['hours', hours, 3_600_000], diff --git a/src/conversion.ts b/src/conversion.ts index 51c529d..c0b3507 100644 --- a/src/conversion.ts +++ b/src/conversion.ts @@ -1,4 +1,13 @@ -import { DAY, HOUR, MILLISECOND, MINUTE, SECOND, WEEK } from './constant.js'; +import { + DAY, + HOUR, + MICROSECOND, + MILLISECOND, + MINUTE, + NANOSECOND, + SECOND, + WEEK, +} from './constant.js'; import { createUnitConverter } from './utils/createUnitConverter.js'; /** @@ -41,3 +50,15 @@ export const seconds = createUnitConverter(SECOND); */ export const milliseconds = createUnitConverter(MILLISECOND); + +/** + * Converts between microseconds and milliseconds. + */ + +export const microseconds = createUnitConverter(MICROSECOND); + +/** + * Converts between nanoseconds and milliseconds. + */ + +export const nanoseconds = createUnitConverter(NANOSECOND); diff --git a/src/index.test.ts b/src/index.test.ts index 5f8c886..098a690 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -9,17 +9,23 @@ describe('api', () => { "DAYS_IN_WEEK": 7, "HOUR": 3600000, "HOURS_IN_DAY": 24, + "MICROSECOND": 0.001, + "MICROSECONDS_IN_A_MILLISECOND": 1000, "MILLISECOND": 1, "MILLISECONDS_IN_A_SECOND": 1000, "MINUTE": 60000, "MINUTES_IN_HOUR": 60, + "NANOSECOND": 0.000001, + "NANOSECONDS_IN_A_MICROSECOND": 1000, "SECOND": 1000, "SECONDS_IN_MINUTE": 60, "WEEK": 604800000, "days": [Function], "hours": [Function], + "microseconds": [Function], "milliseconds": [Function], "minutes": [Function], + "nanoseconds": [Function], "parseDuration": [Function], "seconds": [Function], "toParts": [Function], diff --git a/src/parseDuration.test.ts b/src/parseDuration.test.ts index 1b95ab0..6106537 100644 --- a/src/parseDuration.test.ts +++ b/src/parseDuration.test.ts @@ -27,6 +27,8 @@ const testCases: [string[], number][] = [ [['1m', '2s', '3ms'], 62_003], [['1h', '2m', '3s', '4ms'], 3_723_004], [['1d', '2h', '3m', '4s', '5ms'], 93_784_005], + [['1d', '2h', '3m', '4s', '5ms', '6μs'], 93_784_005.006], + [['1d', '2h', '3m', '4s', '5ms', '6μs', '7ns'], 93_784_005.006_007], ]; describe('parseDuration()', () => { @@ -36,6 +38,8 @@ describe('parseDuration()', () => { expect(parseDuration('1h')).toBe(3_600_000); expect(parseDuration('1d')).toBe(86_400_000); expect(parseDuration('1ms')).toBe(1); + expect(parseDuration('1μs')).toBe(0.001); + expect(parseDuration('1ns')).toBe(0.000_001); }); it.each([ diff --git a/src/parseDuration.ts b/src/parseDuration.ts index 659c584..c941961 100644 --- a/src/parseDuration.ts +++ b/src/parseDuration.ts @@ -1,9 +1,18 @@ -import { days, hours, minutes, seconds } from './conversion.js'; +import { + days, + hours, + microseconds, + minutes, + nanoseconds, + seconds, +} from './conversion.js'; import { UnitConverter } from './utils/createUnitConverter.js'; -type TimeUnit = 'ms' | 's' | 'm' | 'h' | 'd'; +type TimeUnit = 'ns' | 'μs' | 'ms' | 's' | 'm' | 'h' | 'd'; const unitToConverterMap = { + ns: nanoseconds, + μs: microseconds, ms: null, s: seconds, m: minutes, @@ -70,5 +79,3 @@ export const parseDuration = ( return total ?? 0; }; - -parseDuration('1s', false); // $ExpectType number diff --git a/src/toParts.test.ts b/src/toParts.test.ts index d79ef3e..5d3c9b5 100644 --- a/src/toParts.test.ts +++ b/src/toParts.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest'; import { toParts } from './toParts.js'; -import { weeks } from './conversion.js'; describe('toParts()', () => { it('returns the correct parts for a positive duration', () => { @@ -10,8 +9,10 @@ describe('toParts()', () => { days: 1, hours: 20, isNegative: false, + microseconds: 0, milliseconds: 500, minutes: 56, + nanoseconds: 0, seconds: 46, weeks: 1, }); @@ -24,8 +25,42 @@ describe('toParts()', () => { days: 1, hours: 20, isNegative: true, + microseconds: 0, milliseconds: 500, minutes: 56, + nanoseconds: 0, + seconds: 46, + weeks: 1, + }); + }); + + it('returns the correct parts for a positive duration with decimal places', () => { + const result = toParts(766_606_500.003002); + + expect(result).toEqual({ + days: 1, + hours: 20, + isNegative: false, + microseconds: 3, + milliseconds: 500, + minutes: 56, + nanoseconds: 2, + seconds: 46, + weeks: 1, + }); + }); + + it('returns the correct parts for a negative duration with decimal places', () => { + const result = toParts(-766_606_500.003002); + + expect(result).toEqual({ + days: 1, + hours: 20, + isNegative: true, + microseconds: 3, + milliseconds: 500, + minutes: 56, + nanoseconds: 2, seconds: 46, weeks: 1, }); diff --git a/src/toParts.ts b/src/toParts.ts index 2c0dd70..1319902 100644 --- a/src/toParts.ts +++ b/src/toParts.ts @@ -1,11 +1,33 @@ import { DAYS_IN_WEEK, HOURS_IN_DAY, + MICROSECONDS_IN_A_MILLISECOND, MILLISECONDS_IN_A_SECOND, MINUTES_IN_HOUR, + NANOSECONDS_IN_A_MICROSECOND, SECONDS_IN_MINUTE, } from './constant.js'; -import { days, hours, minutes, seconds, weeks } from './conversion.js'; +import { + days, + hours, + microseconds, + minutes, + nanoseconds, + seconds, + weeks, +} from './conversion.js'; + +export interface Parts { + days: number; + hours: number; + isNegative: boolean; + nanoseconds: number; + microseconds: number; + milliseconds: number; + minutes: number; + seconds: number; + weeks: number; +} /** * Converts a duration in milliseconds to an object with properties for each time unit. @@ -14,13 +36,19 @@ import { days, hours, minutes, seconds, weeks } from './conversion.js'; * @returns An object with properties for each time unit */ -export const toParts = (ms: number) => { +export const toParts = (ms: number): Parts => { const absoluteMs = Math.abs(ms); return { days: Math.floor(days.from(absoluteMs) % DAYS_IN_WEEK), hours: Math.floor(hours.from(absoluteMs) % HOURS_IN_DAY), isNegative: ms < 0, + nanoseconds: Math.round( + nanoseconds.from(absoluteMs) % NANOSECONDS_IN_A_MICROSECOND, + ), + microseconds: Math.round( + microseconds.from(absoluteMs) % MICROSECONDS_IN_A_MILLISECOND, + ), milliseconds: Math.round(absoluteMs % MILLISECONDS_IN_A_SECOND), minutes: Math.floor(minutes.from(absoluteMs) % MINUTES_IN_HOUR), seconds: Math.floor(seconds.from(absoluteMs) % SECONDS_IN_MINUTE), diff --git a/src/utilities.ts b/src/utilities.ts index 8868d65..a43a039 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,2 +1,2 @@ export { parseDuration } from './parseDuration.js'; -export { toParts } from './toParts.js'; +export { toParts, type Parts } from './toParts.js';