@arcjet/stable-hash
Advanced tools
| import { FieldHasher, StringWriter, bool, float64, makeHasher, string, stringSliceOrdered, uint32 } from "./hasher.js"; | ||
| //#region src/edge-light.d.ts | ||
| declare const hash: ReturnType<typeof makeHasher>; | ||
| //#endregion | ||
| export { FieldHasher, StringWriter, bool, float64, hash, makeHasher, string, stringSliceOrdered, uint32 }; |
| import { bool, float64, makeHasher, string, stringSliceOrdered, uint32 } from "./hasher.js"; | ||
| //#region src/edge-light.ts | ||
| const hash = makeHasher(crypto.subtle); | ||
| //#endregion | ||
| export { bool, float64, hash, makeHasher, string, stringSliceOrdered, uint32 }; |
| //#region src/hasher.d.ts | ||
| /** | ||
| * Writer. | ||
| */ | ||
| interface StringWriter { | ||
| /** | ||
| * Write data. | ||
| * | ||
| * @param data | ||
| * Value. | ||
| */ | ||
| writeString(data: string): void; | ||
| } | ||
| interface SubtleCryptoLike { | ||
| digest(algorithm: { | ||
| name: string; | ||
| } | string, data: ArrayBufferView<ArrayBufferLike> | ArrayBufferLike): Promise<ArrayBuffer>; | ||
| } | ||
| /** | ||
| * Hash a field. | ||
| */ | ||
| type FieldHasher = (data: StringWriter) => void; | ||
| /** | ||
| * Create a hasher for a boolean. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| declare function bool(key: string, value: boolean): FieldHasher; | ||
| /** | ||
| * Create a hasher for an unsigned 32-bit integer. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| declare function uint32(key: string, value: number): FieldHasher; | ||
| /** | ||
| * Create a hasher for a string. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| declare function string(key: string, value: string): FieldHasher; | ||
| /** | ||
| * Create a hasher for a 64-bit floating point number. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| declare function float64(key: string, value: number): FieldHasher; | ||
| /** | ||
| * Create a hasher for an array of strings. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param values | ||
| * Values. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| declare function stringSliceOrdered(key: string, values: ReadonlyArray<string>): FieldHasher; | ||
| /** | ||
| * Create a hasher. | ||
| * | ||
| * @param subtle | ||
| * Subtle crypto. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| declare function makeHasher(subtle: SubtleCryptoLike): (...hashers: ReadonlyArray<FieldHasher>) => Promise<string>; | ||
| //#endregion | ||
| export { FieldHasher, StringWriter, bool, float64, makeHasher, string, stringSliceOrdered, uint32 }; |
+163
| //#region src/hasher.ts | ||
| var Sha256 = class { | ||
| encoder; | ||
| subtle; | ||
| buf; | ||
| constructor(subtle) { | ||
| this.subtle = subtle; | ||
| this.encoder = new TextEncoder(); | ||
| this.buf = ""; | ||
| } | ||
| writeString(data) { | ||
| this.buf += data; | ||
| } | ||
| async digest() { | ||
| const buf = this.encoder.encode(this.buf); | ||
| const digest = await this.subtle.digest("SHA-256", buf); | ||
| return new Uint8Array(digest); | ||
| } | ||
| }; | ||
| const maxUint32 = 4294967295; | ||
| const fieldSeparator = ":"; | ||
| const itemSeparator = ","; | ||
| /** | ||
| * Create a hasher for a boolean. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function bool(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| if (value) data.writeString("true"); | ||
| else data.writeString("false"); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for an unsigned 32-bit integer. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function uint32(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| if (value > maxUint32) data.writeString("0"); | ||
| else data.writeString(value.toFixed(0)); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for a string. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function string(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| data.writeString(`"`); | ||
| data.writeString(value.replaceAll(`"`, `\\"`)); | ||
| data.writeString(`"`); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for a 64-bit floating point number. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function float64(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| data.writeString(value.toString()); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for an array of strings. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param values | ||
| * Values. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function stringSliceOrdered(key, values) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| data.writeString("["); | ||
| for (const value of Array.from(values).sort()) { | ||
| data.writeString(`"`); | ||
| data.writeString(value.replaceAll(`"`, `\\"`)); | ||
| data.writeString(`"`); | ||
| data.writeString(itemSeparator); | ||
| } | ||
| data.writeString("]"); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher. | ||
| * | ||
| * @param subtle | ||
| * Subtle crypto. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function makeHasher(subtle) { | ||
| /** | ||
| * Hash fields. | ||
| * | ||
| * @param hashers | ||
| * Hashers. | ||
| * @returns | ||
| * Promise to a hash. | ||
| */ | ||
| return async function hash(...hashers) { | ||
| const h = new Sha256(subtle); | ||
| for (const hasher of hashers) { | ||
| hasher(h); | ||
| h.writeString(itemSeparator); | ||
| } | ||
| return hex(await h.digest()); | ||
| }; | ||
| } | ||
| const hexSliceLookupTable = (function() { | ||
| const alphabet = "0123456789abcdef"; | ||
| const table = new Array(256); | ||
| for (let i = 0; i < 16; ++i) { | ||
| const i16 = i * 16; | ||
| for (let j = 0; j < 16; ++j) table[i16 + j] = alphabet[i] + alphabet[j]; | ||
| } | ||
| return table; | ||
| })(); | ||
| function hex(buf) { | ||
| const len = buf.length; | ||
| const start = 0; | ||
| const end = len; | ||
| let out = ""; | ||
| for (let i = start; i < end; ++i) out += hexSliceLookupTable[buf[i]]; | ||
| return out; | ||
| } | ||
| //#endregion | ||
| export { bool, float64, makeHasher, string, stringSliceOrdered, uint32 }; |
| import { FieldHasher, StringWriter, bool, float64, makeHasher, string, stringSliceOrdered, uint32 } from "./hasher.js"; | ||
| //#region src/index.d.ts | ||
| declare const hash: ReturnType<typeof makeHasher>; | ||
| //#endregion | ||
| export { FieldHasher, StringWriter, bool, float64, hash, makeHasher, string, stringSliceOrdered, uint32 }; |
| import { bool, float64, makeHasher, string, stringSliceOrdered, uint32 } from "./hasher.js"; | ||
| import * as crypto from "node:crypto"; | ||
| //#region src/index.ts | ||
| const hash = makeHasher(crypto.subtle); | ||
| //#endregion | ||
| export { bool, float64, hash, makeHasher, string, stringSliceOrdered, uint32 }; |
| import { FieldHasher, StringWriter, bool, float64, makeHasher, string, stringSliceOrdered, uint32 } from "./hasher.js"; | ||
| //#region src/workerd.d.ts | ||
| declare const hash: ReturnType<typeof makeHasher>; | ||
| //#endregion | ||
| export { FieldHasher, StringWriter, bool, float64, hash, makeHasher, string, stringSliceOrdered, uint32 }; |
| import { bool, float64, makeHasher, string, stringSliceOrdered, uint32 } from "./hasher.js"; | ||
| //#region src/workerd.ts | ||
| const hash = makeHasher(crypto.subtle); | ||
| //#endregion | ||
| export { bool, float64, hash, makeHasher, string, stringSliceOrdered, uint32 }; |
+41
-39
| { | ||
| "name": "@arcjet/stable-hash", | ||
| "version": "1.7.0", | ||
| "version": "1.8.0-rc.0", | ||
| "description": "Arcjet stable hashing utility", | ||
@@ -8,12 +8,6 @@ "keywords": [ | ||
| "hash", | ||
| "utility", | ||
| "util" | ||
| "util", | ||
| "utility" | ||
| ], | ||
| "license": "Apache-2.0", | ||
| "homepage": "https://arcjet.com", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/arcjet/arcjet-js.git", | ||
| "directory": "stable-hash" | ||
| }, | ||
| "bugs": { | ||
@@ -23,2 +17,3 @@ "url": "https://github.com/arcjet/arcjet-js/issues", | ||
| }, | ||
| "license": "Apache-2.0", | ||
| "author": { | ||
@@ -29,43 +24,50 @@ "name": "Arcjet", | ||
| }, | ||
| "engines": { | ||
| "node": ">=22.21.0 <23 || >=24.5.0" | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/arcjet/arcjet-js.git", | ||
| "directory": "stable-hash" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "type": "module", | ||
| "main": "./index.js", | ||
| "types": "./index.d.ts", | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| "edge-light": "./edge-light.js", | ||
| "workerd": "./workerd.js", | ||
| "default": "./index.js" | ||
| ".": { | ||
| "edge-light": { | ||
| "types": "./dist/edge-light.d.ts", | ||
| "default": "./dist/edge-light.js" | ||
| }, | ||
| "workerd": { | ||
| "types": "./dist/workerd.d.ts", | ||
| "default": "./dist/workerd.js" | ||
| }, | ||
| "default": { | ||
| "types": "./dist/index.d.ts", | ||
| "default": "./dist/index.js" | ||
| } | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "files": [ | ||
| "edge-light.d.ts", | ||
| "edge-light.js", | ||
| "hasher.d.ts", | ||
| "hasher.js", | ||
| "index.d.ts", | ||
| "index.js", | ||
| "workerd.d.ts", | ||
| "workerd.js" | ||
| ], | ||
| "publishConfig": { | ||
| "access": "public", | ||
| "tag": "latest" | ||
| }, | ||
| "scripts": { | ||
| "build": "rollup --config rollup.config.js", | ||
| "lint": "eslint .", | ||
| "test-api": "node --test -- test/*.test.js", | ||
| "test-coverage": "node --experimental-test-coverage --test -- test/*.test.js", | ||
| "test": "npm run build && npm run lint && npm run test-coverage" | ||
| "build": "tsdown", | ||
| "typecheck": "tsgo --noEmit", | ||
| "test-api": "node --test -- test/*.test.ts", | ||
| "test-coverage": "node --experimental-test-coverage --test -- test/*.test.ts", | ||
| "test": "npm run build && npm run test-coverage" | ||
| }, | ||
| "dependencies": {}, | ||
| "devDependencies": { | ||
| "@arcjet/eslint-config": "1.7.0", | ||
| "@arcjet/rollup-config": "1.7.0", | ||
| "@rollup/wasm-node": "4.62.2", | ||
| "@types/node": "22.19.21", | ||
| "eslint": "9.39.4", | ||
| "typescript": "5.9.3" | ||
| "tsdown": "0.22.3", | ||
| "typescript": "6.0.3" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public", | ||
| "tag": "latest" | ||
| "engines": { | ||
| "node": ">=22.21.0 <23 || >=24.5.0" | ||
| } | ||
| } |
| export * from "./hasher.js"; | ||
| export declare const hash: (...hashers: ReadonlyArray<import("./hasher.js").FieldHasher>) => Promise<string>; |
| import { makeHasher } from './hasher.js'; | ||
| export { bool, float64, string, stringSliceOrdered, uint32 } from './hasher.js'; | ||
| const hash = makeHasher(crypto.subtle); | ||
| export { hash, makeHasher }; |
-86
| /** | ||
| * Writer. | ||
| */ | ||
| export interface StringWriter { | ||
| /** | ||
| * Write data. | ||
| * | ||
| * @param data | ||
| * Value. | ||
| */ | ||
| writeString(data: string): void; | ||
| } | ||
| interface SubtleCryptoLike { | ||
| digest(algorithm: { | ||
| name: string; | ||
| } | string, data: ArrayBufferView<ArrayBufferLike> | ArrayBufferLike): Promise<ArrayBuffer>; | ||
| } | ||
| /** | ||
| * Hash a field. | ||
| */ | ||
| export type FieldHasher = (data: StringWriter) => void; | ||
| /** | ||
| * Create a hasher for a boolean. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| export declare function bool(key: string, value: boolean): FieldHasher; | ||
| /** | ||
| * Create a hasher for an unsigned 32-bit integer. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| export declare function uint32(key: string, value: number): FieldHasher; | ||
| /** | ||
| * Create a hasher for a string. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| export declare function string(key: string, value: string): FieldHasher; | ||
| /** | ||
| * Create a hasher for a 64-bit floating point number. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| export declare function float64(key: string, value: number): FieldHasher; | ||
| /** | ||
| * Create a hasher for an array of strings. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param values | ||
| * Values. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| export declare function stringSliceOrdered(key: string, values: ReadonlyArray<string>): FieldHasher; | ||
| /** | ||
| * Create a hasher. | ||
| * | ||
| * @param subtle | ||
| * Subtle crypto. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| export declare function makeHasher(subtle: SubtleCryptoLike): (...hashers: ReadonlyArray<FieldHasher>) => Promise<string>; | ||
| export {}; |
-202
| class Sha256 { | ||
| encoder; | ||
| subtle; | ||
| buf; | ||
| constructor(subtle) { | ||
| this.subtle = subtle; | ||
| this.encoder = new TextEncoder(); | ||
| this.buf = ""; | ||
| } | ||
| writeString(data) { | ||
| this.buf += data; | ||
| } | ||
| async digest() { | ||
| const buf = this.encoder.encode(this.buf); | ||
| const digest = await this.subtle.digest("SHA-256", buf); | ||
| return new Uint8Array(digest); | ||
| } | ||
| } | ||
| // After this, it needs to wrap to 0 | ||
| const maxUint32 = 4294967295; | ||
| const fieldSeparator = ":"; | ||
| const itemSeparator = ","; | ||
| /** | ||
| * Create a hasher for a boolean. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function bool(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| if (value) { | ||
| data.writeString("true"); | ||
| } | ||
| else { | ||
| data.writeString("false"); | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for an unsigned 32-bit integer. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function uint32(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| if (value > maxUint32) { | ||
| data.writeString("0"); | ||
| } | ||
| else { | ||
| data.writeString(value.toFixed(0)); | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for a string. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function string(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| data.writeString(`"`); | ||
| data.writeString(value.replaceAll(`"`, `\\"`)); | ||
| data.writeString(`"`); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for a 64-bit floating point number. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param value | ||
| * Value. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function float64(key, value) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| data.writeString(value.toString()); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher for an array of strings. | ||
| * | ||
| * @param key | ||
| * Key. | ||
| * @param values | ||
| * Values. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function stringSliceOrdered(key, values) { | ||
| return (data) => { | ||
| data.writeString(key); | ||
| data.writeString(fieldSeparator); | ||
| data.writeString("["); | ||
| for (const value of Array.from(values).sort()) { | ||
| data.writeString(`"`); | ||
| data.writeString(value.replaceAll(`"`, `\\"`)); | ||
| data.writeString(`"`); | ||
| data.writeString(itemSeparator); | ||
| } | ||
| data.writeString("]"); | ||
| }; | ||
| } | ||
| /** | ||
| * Create a hasher. | ||
| * | ||
| * @param subtle | ||
| * Subtle crypto. | ||
| * @returns | ||
| * Hasher. | ||
| */ | ||
| function makeHasher(subtle) { | ||
| /** | ||
| * Hash fields. | ||
| * | ||
| * @param hashers | ||
| * Hashers. | ||
| * @returns | ||
| * Promise to a hash. | ||
| */ | ||
| return async function hash(...hashers) { | ||
| const h = new Sha256(subtle); | ||
| for (const hasher of hashers) { | ||
| hasher(h); | ||
| h.writeString(itemSeparator); | ||
| } | ||
| const digest = await h.digest(); | ||
| return hex(digest); | ||
| }; | ||
| } | ||
| // Hex encoding logic from https://github.com/feross/buffer but adjusted for | ||
| // our use. | ||
| // | ||
| // Licensed: The MIT License (MIT) | ||
| // | ||
| // Copyright (c) Feross Aboukhadijeh, and other contributors. | ||
| // | ||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| // of this software and associated documentation files (the "Software"), to deal | ||
| // in the Software without restriction, including without limitation the rights | ||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| // copies of the Software, and to permit persons to whom the Software is | ||
| // furnished to do so, subject to the following conditions: | ||
| // | ||
| // The above copyright notice and this permission notice shall be included in | ||
| // all copies or substantial portions of the Software. | ||
| // | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| // THE SOFTWARE. | ||
| // https://github.com/feross/buffer/blob/5857e295f4d37e3ad02c3abcbf7e8e5ef51f3be6/index.js#L2096-L2106 | ||
| const hexSliceLookupTable = (function () { | ||
| const alphabet = "0123456789abcdef"; | ||
| const table = new Array(256); | ||
| for (let i = 0; i < 16; ++i) { | ||
| const i16 = i * 16; | ||
| for (let j = 0; j < 16; ++j) { | ||
| table[i16 + j] = alphabet[i] + alphabet[j]; | ||
| } | ||
| } | ||
| return table; | ||
| })(); | ||
| // https://github.com/feross/buffer/blob/5857e295f4d37e3ad02c3abcbf7e8e5ef51f3be6/index.js#L1085-L1096 | ||
| function hex(buf) { | ||
| const len = buf.length; | ||
| const start = 0; | ||
| const end = len; | ||
| let out = ""; | ||
| for (let i = start; i < end; ++i) { | ||
| out += hexSliceLookupTable[buf[i]]; | ||
| } | ||
| return out; | ||
| } | ||
| export { bool, float64, makeHasher, string, stringSliceOrdered, uint32 }; |
| export * from "./hasher.js"; | ||
| export declare const hash: (...hashers: ReadonlyArray<import("./hasher.js").FieldHasher>) => Promise<string>; |
-7
| import * as crypto from 'node:crypto'; | ||
| import { makeHasher } from './hasher.js'; | ||
| export { bool, float64, string, stringSliceOrdered, uint32 } from './hasher.js'; | ||
| const hash = makeHasher(crypto.subtle); | ||
| export { hash, makeHasher }; |
| export * from "./hasher.js"; | ||
| export declare const hash: (...hashers: ReadonlyArray<import("./hasher.js").FieldHasher>) => Promise<string>; |
| import { makeHasher } from './hasher.js'; | ||
| export { bool, float64, string, stringSliceOrdered, uint32 } from './hasher.js'; | ||
| const hash = makeHasher(crypto.subtle); | ||
| export { hash, makeHasher }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
3
-50%25152
-5.13%278
-9.15%1
Infinity%