diff --git a/.gitignore b/.gitignore index d8af9a6..a174b37 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,6 @@ # TODO: where does this rule come from? docs/_book -# TODO: where does this rule come from? -test/ - # ---> Node # Logs logs diff --git a/packages/logic/package.json b/packages/logic/package.json index 7a59412..5a782b4 100644 --- a/packages/logic/package.json +++ b/packages/logic/package.json @@ -8,6 +8,15 @@ "build": "tsc -p tsconfig.json", "dev": "node -e \"console.log('dev not configured')\"", "lint": "node -e \"console.log('lint not configured')\"", - "test": "node -e \"console.log('test not configured')\"" + "test": "vitest run --config vitest.config.ts" + }, + "dependencies": { + "js-sha512": "^0.8.0" + }, + "devDependencies": { + "chai": "^4.3.10", + "chai-bytes": "^0.1.2", + "lodash-es": "^4.17.21", + "vitest": "^2.0.5" } } diff --git a/packages/logic/src/index.ts b/packages/logic/src/index.ts index ef09579..99fd3aa 100644 --- a/packages/logic/src/index.ts +++ b/packages/logic/src/index.ts @@ -2,3 +2,9 @@ export * from './domain/entities'; export * from './ports/rng'; export * from './ports/world'; export * from './triggers'; +export * from './util/BytesLike'; +export * from './util/convertBytesLikeToArrayBuffer'; +export * from './util/convertBytesLikeToUint8Array'; +export * from './util/LiteHashDRBG'; +export * from './util/RNG'; +export * from './util/RandUtil'; diff --git a/packages/logic/src/util/BytesLike.ts b/packages/logic/src/util/BytesLike.ts new file mode 100644 index 0000000..e435357 --- /dev/null +++ b/packages/logic/src/util/BytesLike.ts @@ -0,0 +1,2 @@ +export type Bytes = ArrayBuffer | DataView | Uint8Array; +export type BytesLike = Bytes | string; diff --git a/packages/logic/src/util/LiteHashDRBG.ts b/packages/logic/src/util/LiteHashDRBG.ts new file mode 100644 index 0000000..f511ecd --- /dev/null +++ b/packages/logic/src/util/LiteHashDRBG.ts @@ -0,0 +1,234 @@ +import type { RNG } from './RNG'; + +import { sha512 } from 'js-sha512'; + +import { convertBytesLikeToUint8Array } from './convertBytesLikeToUint8Array'; +import type { BytesLike } from './BytesLike'; + +const maxRngSupportBit = 53; +const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11 +const maxIntMore1 = 0x20_0000_0000_0000n; //NOTE: b 0, 10000110100, 00...00 +const maxIntMore1f = Number(maxIntMore1); +export const bufferByteSize = 512 / 8; //SHA512 + +const intBitMapMask = new Map([ + [0x1n, 1], + [0x3n, 2], + [0x7n, 3], + [0xfn, 4], + [0x1fn, 5], + [0x3fn, 6], + [0x7fn, 7], + [0xffn, 8], + [0x1ffn, 9], + [0x3ffn, 10], + [0x7ffn, 11], + [0xfffn, 12], + [0x1fffn, 13], + [0x3fffn, 14], + [0x7fffn, 15], + [0xffffn, 16], + [0x1ffffn, 17], + [0x3ffffn, 18], + [0x7ffffn, 19], + [0xfffffn, 20], + [0x1fffffn, 21], + [0x3fffffn, 22], + [0x7fffffn, 23], + [0xffffffn, 24], + [0x1ffffffn, 25], + [0x3ffffffn, 26], + [0x7ffffffn, 27], + [0xfffffffn, 28], + [0x1fffffffn, 29], + [0x3fffffffn, 30], + [0x7fffffffn, 31], + [0xffffffffn, 32], + [0x1ffffffffn, 33], + [0x3ffffffffn, 34], + [0x7ffffffffn, 35], + [0xfffffffffn, 36], + [0x1fffffffffn, 37], + [0x3fffffffffn, 38], + [0x7fffffffffn, 39], + [0xffffffffffn, 40], + [0x1ffffffffffn, 41], + [0x3ffffffffffn, 42], + [0x7ffffffffffn, 43], + [0xfffffffffffn, 44], + [0x1fffffffffffn, 45], + [0x3fffffffffffn, 46], + [0x7fffffffffffn, 47], + [0xffffffffffffn, 48], + [0x1ffffffffffffn, 49], + [0x3ffffffffffffn, 50], + [0x7ffffffffffffn, 51], + [0xfffffffffffffn, 52], + [0x1fffffffffffffn, 53], +]); + +function calcBitMask(n: bigint): bigint { + n |= n >> 1n; + n |= n >> 2n; + n |= n >> 4n; + n |= n >> 8n; + n |= n >> 16n; + n |= n >> 32n; + + return n; +} + +// SHA-512 기반 DRBG 구현 +export class LiteHashDRBG implements RNG { + + protected buffer!: ArrayBuffer; + protected bufferIdx!: number; + protected hq: DataView; + protected hqIdxPos: number; + + public constructor(protected seed: BytesLike, protected stateIdx = 0, bufferIdx = 0) { + if (bufferIdx < 0) { + throw new Error(`bufferIdx ${bufferIdx} < 0`); + } + if (bufferIdx >= bufferByteSize) { + throw new Error(`bufferidx ${bufferIdx} >= ${bufferByteSize}`); + } + if (stateIdx < 0) { + throw new Error(`stateIdx ${stateIdx} < 0`); + } + + const seedU8 = convertBytesLikeToUint8Array(seed); + const hqBuffer = new ArrayBuffer(seedU8.byteLength + 4); + const hqU8 = new Uint8Array(hqBuffer); + + hqU8.set(seedU8, 0); + this.hq = new DataView(hqBuffer); + this.hqIdxPos = seedU8.byteLength; + + this.genNextBlock(); + this.bufferIdx = bufferIdx; + } + + protected genNextBlock(): void { + this.hq.setUint32(this.hqIdxPos, this.stateIdx, true); + const digest = sha512.arrayBuffer(this.hq.buffer); + this.buffer = digest; + this.bufferIdx = 0; + this.stateIdx += 1; + } + + public getMaxInt(): number { + return maxInt; + } + + public nextBytes(bytes: number, baseBytes?: number): Uint8Array { + bytes |= 0; + if (bytes <= 0) { + throw new Error(`${bytes} <= 0`); + } + + if (this.bufferIdx + bytes <= bufferByteSize) { + if (baseBytes === undefined || bytes >= baseBytes) { + const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes); + this.bufferIdx += bytes; + if (this.bufferIdx === bufferByteSize) { + this.genNextBlock(); + } + return new Uint8Array(result); + } + + const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes)); + const result = new Uint8Array(resultBuffer); + result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes)); + this.bufferIdx += bytes; + if (this.bufferIdx === bufferByteSize) { + this.genNextBlock(); + } + return result; + } + + const resultBuffer = new ArrayBuffer(baseBytes ? Math.max(bytes, baseBytes) : bytes); + const result = new Uint8Array(resultBuffer); + + result.set(new Uint8Array(this.buffer, this.bufferIdx)); + let offset = bufferByteSize - this.bufferIdx; + let remain = bytes - offset; + + while (remain > bufferByteSize) { + this.genNextBlock(); + result.set(new Uint8Array(this.buffer), offset); + offset += bufferByteSize; + remain -= bufferByteSize; + } + + this.genNextBlock(); + if (remain === 0) { + return result; + } + + result.set(new Uint8Array(this.buffer, 0, remain), offset); + this.bufferIdx = remain; + return result; + } + + public nextBits(bits: number, baseBytes?: number): Uint8Array { + bits |= 0; + const bytes = (bits + 7) >> 3; + const headBits = bits & 0x7; + + const result = this.nextBytes(bytes, baseBytes); + if (headBits === 0) { + return result; + } + + result[bytes - 1] &= 0xff >> (8 - headBits); + return result; + } + + protected _nextInt(bits: number): bigint { + const buffer = this.nextBits(bits, 8); + const dataView = new DataView(buffer.buffer); + return dataView.getBigUint64(0, true); + } + + public nextInt(max?: number): number { + if (max === undefined || max === maxInt) { + return Number(this._nextInt(maxRngSupportBit)); + } + if (max > maxInt) { + throw new Error('Over max int'); + } + if (max === 0) { + return 0; + } + if (max < 0) { + return -this.nextInt(-max); + } + + const mask = calcBitMask(BigInt(max)); + const bits = intBitMapMask.get(mask) as number; + + let n = Number(this._nextInt(bits)); + while (n > max) { + n = Number(this._nextInt(bits)); + } + return n; + } + + public nextFloat1(): number { + // eslint-disable-next-line no-constant-condition + while (true) { + const nInt = this._nextInt(maxRngSupportBit + 1); + if (nInt < maxIntMore1) { + return Number(nInt) / maxIntMore1f; + } + if (nInt === maxIntMore1) { + return 1; + } + } + } + + public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG { + return new LiteHashDRBG(seed, stateIdx); + } +} diff --git a/packages/logic/src/util/RNG.ts b/packages/logic/src/util/RNG.ts new file mode 100644 index 0000000..613be4f --- /dev/null +++ b/packages/logic/src/util/RNG.ts @@ -0,0 +1,13 @@ +export interface RNG { + + /** + * nextInt()가 반환 가능한 최댓값 + */ + getMaxInt(): number; + + nextBytes(bytes: number): Uint8Array; + nextBits(bits: number): Uint8Array; + + nextInt(max?: number): number; + nextFloat1(): number; +} diff --git a/packages/logic/src/util/RandUtil.ts b/packages/logic/src/util/RandUtil.ts new file mode 100644 index 0000000..34db5a4 --- /dev/null +++ b/packages/logic/src/util/RandUtil.ts @@ -0,0 +1,145 @@ +import type { RNG } from './RNG'; + +// RNG 유틸리티 모음 +export class RandUtil { + constructor(protected rng: RNG) { + + } + + public nextFloat1(): number { + return this.rng.nextFloat1(); + } + + public nextRange(min: number, max: number): number { + const range = max - min; + return this.nextFloat1() * (range) + min; + } + + public nextRangeInt(min: number, max: number): number { + const range = max - min; + return this.rng.nextInt(range) + min; + } + + public nextInt(max?: number): number { + return this.rng.nextInt(max); + } + + public nextBit(): boolean { + const view = new DataView(this.rng.nextBits(1) as ArrayBufferLike); + return view.getUint8(0) != 0; + } + + public nextBool(prob = 0.5): boolean { + if (prob >= 1) { + return true; + } + if (prob === 0.5) { + return this.nextBit(); + } + if (prob <= 0) { + return false; + } + return this.nextFloat1() < prob; + } + + public shuffle(srcArray: T[]): T[] { + const cnt = srcArray.length; + if (cnt === 0) { + return []; + } + if (cnt > this.rng.getMaxInt()) { + throw 'Invalid random int range'; + } + + const result: T[] = Array.from(srcArray); + for (let srcIdx = 0; srcIdx < cnt; srcIdx += 1) { + const destIdx = this.rng.nextInt(cnt - srcIdx - 1) + srcIdx; + if (srcIdx === destIdx) { + continue; + } + [result[srcIdx], result[destIdx]] = [result[destIdx], result[srcIdx]]; + } + + return result; + } + + //Object는 integer key에 예외가 있어 shuffleAssoc은 없음 + + public choice(items: T[] | Record | Set): T { + if (items instanceof Array) { + if (items.length === 0) { + throw new Error('Empty items'); + } + const idx = this.rng.nextInt(items.length - 1); + return items[idx]; + } + + if (items instanceof Set) { + return this.choice(Array.from(items.values())); + } + + return items[this.choice(Array.from(Object.keys(items)))]; + } + + public choiceUsingWeight(items: Record): string | number { + if (Object.keys(items).length === 0) { + throw new Error('Empty items'); + } + let sum = 0; + for (const value of Object.values(items)) { + if (value <= 0) { + continue; + } + sum += value; + } + + let rd = this.nextFloat1() * sum; + + for (const [item, value] of Object.entries(items)) { + if (value <= 0) { + if (rd <= 0) { + return item; + } + continue; + } + + if (rd <= value) { + return item; + } + rd -= value; + } + + throw new Error('Unreacheable'); + } + + public choiceUsingWeightPair(items: [T, number][]): T { + if (items.length === 0) { + throw new Error('Empty items'); + } + let sum = 0; + for (const [, value] of items) { + if (value <= 0) { + continue; + } + sum += value; + } + + let rd = this.nextFloat1() * sum; + + for (const [item, value] of items) { + if (value <= 0) { + if (rd <= 0) { + return item; + } + continue; + } + + if (rd <= value) { + return item; + } + rd -= value; + } + + throw new Error('Unreacheable'); + } +} diff --git a/packages/logic/src/util/convertBytesLikeToArrayBuffer.ts b/packages/logic/src/util/convertBytesLikeToArrayBuffer.ts new file mode 100644 index 0000000..f7326f7 --- /dev/null +++ b/packages/logic/src/util/convertBytesLikeToArrayBuffer.ts @@ -0,0 +1,17 @@ +import type { BytesLike } from './BytesLike'; + +export function convertBytesLikeToArrayBuffer(data: BytesLike, encodeUTF8 = true): ArrayBuffer { + if (data instanceof ArrayBuffer) { + return data; + } + if (data instanceof Uint8Array) { + return data.buffer; + } + if (typeof (data) === 'string') { + if (encodeUTF8) { + return (new TextEncoder()).encode(data).buffer; + } + return new Uint8Array(data.split('').map((s) => s.codePointAt(0) as number)).buffer; + } + return data.buffer; +} diff --git a/packages/logic/src/util/convertBytesLikeToUint8Array.ts b/packages/logic/src/util/convertBytesLikeToUint8Array.ts new file mode 100644 index 0000000..c3c27ca --- /dev/null +++ b/packages/logic/src/util/convertBytesLikeToUint8Array.ts @@ -0,0 +1,17 @@ +import type { BytesLike } from './BytesLike'; + +export function convertBytesLikeToUint8Array(data: BytesLike, encodeUTF8 = true): Uint8Array { + if (data instanceof Uint8Array) { + return data; + } + if (data instanceof ArrayBuffer) { + return new Uint8Array(data); + } + if (typeof (data) === 'string') { + if (encodeUTF8) { + return (new TextEncoder()).encode(data); + } + return new Uint8Array(data.split('').map((s) => s.codePointAt(0) as number)); + } + return new Uint8Array(data.buffer); +} diff --git a/packages/logic/test/rng.test.ts b/packages/logic/test/rng.test.ts new file mode 100644 index 0000000..ad28acd --- /dev/null +++ b/packages/logic/test/rng.test.ts @@ -0,0 +1,370 @@ +import chai, { assert } from 'chai'; +import chaiBytes from 'chai-bytes'; +import { bufferByteSize, LiteHashDRBG } from '../src/util/LiteHashDRBG'; +import { RandUtil } from '../src/util/RandUtil'; +import { convertBytesLikeToArrayBuffer } from '../src/util/convertBytesLikeToArrayBuffer'; +import { convertBytesLikeToUint8Array as toBytes } from '../src/util/convertBytesLikeToUint8Array'; +import _ from 'lodash-es'; + +chai.use(chaiBytes); + +type Bytes = ArrayBuffer | DataView | Uint8Array; +type MaybeBytes = Bytes | string; + +function fillBlock(body: MaybeBytes, filler: MaybeBytes = '\0', length = bufferByteSize): Uint8Array { + const u8Body = toBytes(body); + const u8Filler = toBytes(filler, false); + + if (u8Filler.byteLength < 1) { + throw new Error('filler must have length'); + } + + const buffer = new Uint8Array(length); + buffer.set(u8Body, 0); + let bufferIdx = u8Body.byteLength; + + while (bufferIdx + u8Filler.byteLength < length) { + buffer.set(u8Filler, bufferIdx); + bufferIdx += u8Filler.byteLength; + } + + if (bufferIdx < length) { + const slice = new Uint8Array(u8Filler.buffer, u8Filler.byteOffset, length - bufferIdx); + buffer.set(slice, bufferIdx); + } + + return buffer; +} + + +class DummyBlockRNG extends LiteHashDRBG { + private repeatBlockCnt: number; + private repeatBlock: ArrayBuffer[]; + + public constructor(repeatBlock: MaybeBytes[], stateIdx = 0) { + super('x'); + + this.repeatBlock = []; + for (const rawBlock of repeatBlock) { + const block = convertBytesLikeToArrayBuffer(rawBlock); + if (block.byteLength !== bufferByteSize) { + throw new Error; + } + this.repeatBlock.push(block); + } + this.repeatBlockCnt = this.repeatBlock.length; + this.stateIdx = stateIdx; + this.bufferIdx = 0; + this.genNextBlock(); + } + + protected genNextBlock() { + if (!this.repeatBlock) { + return; + } + this.buffer = this.repeatBlock[this.stateIdx]; + this.bufferIdx = 0; + this.stateIdx = (this.stateIdx + 1) % this.repeatBlockCnt; + } +} + +const fixedKey = 'HelloWorld'; + + +describe('RNGtestDummy', () => { + const rng = new DummyBlockRNG([ + fillBlock('', "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff") + ]); + + it('BasicConvert', () => { + assert.equal(toBytes("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", false).length, 16); + }); + it('SimpleByte', () => { + assert.equalBytes(toBytes("\x00", false), rng.nextBytes(1), 'b1'); + assert.equalBytes(toBytes("\x11\x22", false), rng.nextBytes(2), 'b2'); + assert.equalBytes(toBytes("\x33\x44\x55", false), rng.nextBytes(3), 'b3'); + assert.equalBytes(toBytes("\x66\x77\x88\x99", false), rng.nextBytes(4), 'b4'); + }); + + it('OverflowBlock', () => { + for (const idx in _.range(16)) { + assert.equalBytes( + toBytes("\xaa\xbb\xcc\xdd\xee\xff\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99", false), + rng.nextBytes(16) + ); + } + }); + + it('MultiBlock', () => { + assert.equalBytes( + fillBlock('', "\xaa\xbb\xcc\xdd\xee\xff\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99", bufferByteSize * 2), + rng.nextBytes(bufferByteSize * 2) + ); + }); + + it('bitTest', () => { + assert.equalBytes(toBytes("\x00", false), rng.nextBits(1)); //aa + assert.equalBytes(toBytes("\x01", false), rng.nextBits(1)); //bb + assert.equalBytes(toBytes("\xcc", false), rng.nextBits(8)); //cc + assert.equalBytes(toBytes("\xdd\x02", false), rng.nextBits(10)); //ddee + assert.equalBytes(toBytes("\x7f", false), rng.nextBits(7)); //ff + assert.equalBytes(toBytes("\x00\x11\x22\x33\x44\x55\x06", false), rng.nextBits(53)); + }); + + it('int', () => { + assert.equal(0x77, rng.nextInt(0xff)); + assert.equal(0x9988, rng.nextInt((1 << 16) - 1)); + assert.equal(0xddccbbaa, rng.nextInt(0xffffffff)); + assert.equal(0x0433221100ffee, rng.nextInt()); + assert.equal(0x05, rng.nextInt(0x0f)); //55 + assert.equal(0x06, rng.nextInt(0x12)); //66 + assert.equal(0x08, rng.nextInt(99)); //77(119 -> 7bit) -> 88(136 -> 8bit -> 8) + assert.equal(0x99, rng.nextInt(0x99)); //99 + assert.equal(0xaa, rng.nextInt(0xaa)); //aa (fit Max) + }); + + it('float', () => { + const floatMax = 2 ** 53; + const fa = rng.nextFloat1(); + assert.equal(0x1100ffeeddccbb / floatMax, fa); + assert.isTrue(0.5313720384 > fa); + assert.isTrue(0.5313720383 < fa); + const fb = rng.nextFloat1(); + assert.equal(0x08776655443322 / floatMax, fb); + }); +}); + +describe('RandUtilDummy', () => { + it('shuffle', () => { + const rng = new DummyBlockRNG([fillBlock('', '\x17\x16\x15\x14\x13\x12\x11\x10')]); + const randUtil = new RandUtil(rng); + /** + * 7, [7,1,2,3,4,5,6,0] + * 6, [7,0,2,3,4,5,6,1] + * 5, [7,0,1,3,4,5,6,2] + * 4, [7,0,1,2,4,5,6,3] + * 3, [7,0,1,2,3,5,6,4] + * 2, [7,0,1,2,3,4,6,5] + * 1, [7,0,1,2,3,4,5,6] + */ + assert.deepEqual( + randUtil.shuffle(Array.from(_.range(8))), + [7, 0, 1, 2, 3, 4, 5, 6] + ); + + /** + * 0, [0,1,2,3,4,5,6,7,8,9] + * 7, [0,8,2,3,4,5,6,7,1,9] + * 6, [0,8,1,3,4,5,6,7,2,9] + * 5, [0,8,1,2,4,5,6,7,3,9] + * 4, [0,8,1,2,3,5,6,7,4,9] + * 3, [0,8,1,2,3,4,6,7,5,9] + * 2, [0,8,1,2,3,4,5,7,6,9] + * 1, [0,8,1,2,3,4,5,6,7,9] + * 0, [0,8,1,2,3,4,5,6,7,9] + */ + assert.deepEqual( + randUtil.shuffle(Array.from(_.range(10))), + [0, 8, 1, 2, 3, 4, 5, 6, 7, 9] + ); + }); + + const rng = new DummyBlockRNG([fillBlock('', '\x17\x16\x15\x14\x13\x12\x11\x10')]); + const randUtil = new RandUtil(rng); + it('choice', () => { + + + //0x17(7), 0x16(6) + assert.equal(randUtil.choice([0, 1, 2, 3, 4, 5]), 5); + + //0x15(5), Set 순서 유지 + assert.equal(randUtil.choice(new Set([5, 3, 1, 2, 8, 0])), 8); + + //0x14(4), 정렬 순서상 숫자(소-대) > 문자열(삽입순) > 심볼 순서 + assert.equal(randUtil.choice({ c: 'c', a: 'a', b: 'b', 4: 'x', 2: 't', '3': 'q' }), 'c'); + + }); + + it('choiceUsingWeight', () => { + //0.6275740099377194 * 38.1 = 23.91 + assert.equal(randUtil.choiceUsingWeight({ + a: 0.1, + b: 10, + tt: 2, + x: -1, + c: 20, + d: 0, + e: 6 + }), 'c'); + + //0.658946544056166 + assert.equal(randUtil.choiceUsingWeightPair([ + ['xx', 10], + ]), 'xx'); + + //0.6903152783785083 * 27.3 = 18.84560709973328 + assert.equal(randUtil.choiceUsingWeightPair([ + ['e', 10], + ['d', 4], + ['c', 0.1], + ['baba', 0.2], + ['q', 9], + ['xt', 4] + ]), 'q'); + }); +}); + +describe('RNGexpectedError', () => { + const rng = new LiteHashDRBG(fixedKey); + it('nextBits0', () => { + assert.throw(() => rng.nextBits(0)); + }); + it('nextBits-1', () => { + assert.throw(() => rng.nextBits(-1)); + }); + + it('nextBytes0', () => { + assert.throw(() => rng.nextBytes(0)); + }); + it('nextBytes-1', () => { + assert.throw(() => rng.nextBytes(-1)); + }); + + const randUtil = new RandUtil(rng); + it('utilEmptyChoice', () => { + assert.throw(() => randUtil.choice([])); + }); + it('utilEmptyChoiceUsingWeight', () => { + assert.throw(() => randUtil.choiceUsingWeight({})); + }); + it('utilEmptyChoiceUsingWeightPair', () => { + assert.throw(() => randUtil.choiceUsingWeightPair([])); + }); +}); + +describe('RNGAcceptable', () => { + const rng = new LiteHashDRBG(fixedKey); + it('RNG', () => { + rng.nextInt(0); + rng.nextInt(2 ** 53 - 1); + rng.nextBytes(65); + rng.nextBits(512); + }); + + const randUtil = new RandUtil(rng); + it('RandUtil', () => { + randUtil.choice([0, 0, 0]); + randUtil.choiceUsingWeight({ + 0: 0, + 1: -1 + }); + randUtil.choiceUsingWeightPair([ + [0, 0], + [1, 0], + [2, -2] + ]); + randUtil.nextBool(1.1); + randUtil.nextBool(-0.1); + randUtil.shuffle([]); + randUtil.shuffle([1]); + randUtil.nextRange(0, 0); + randUtil.nextRangeInt(0, 0); + randUtil.nextRange(1, -1); + randUtil.nextRangeInt(1, -1); + }); + + it('RNGLong', () => { + const longKey = fixedKey; + for (const _a of _.range(8)) { + longKey.concat(longKey); + } + const rngLong = new LiteHashDRBG(longKey); + for (const _a of _.range(10)) { + rngLong.nextBytes(16); + } + }); +}); + +/* Python TestVector +import hashlib +import struct + +fixedKey = 'HelloWorld'.encode('utf-8') + +def hash(key, idx): + idxV = struct.pack(" { + + //JS - PHP 일치 확인 정도로. + + const testVector = Buffer.from([ + '24d9ccd648556255fd0ee9f5b29918de90617341958b3b354d572167e4dee02b757816a2bbe0b502c52413ffd384381a9d7b4e193df6f4345d6a95e111d661c4', + '2e9264512f6f4b080cf1376b74fab6878ecf4a6e185942d2e5b22cf923885b9952d40601a414225d6901417fd4ce9368ac77e4a63d3fc9b58ab952bb8c33f165', + '8e2ebf5af6283a1b18f4c044c86c20d02be3890613c4cc8b7c6b7b35581263b972a82630df69a9289988422d7c3a9be5edf78d5de16fabd01e5dd4e458068d8a', + '398596047ba547bfe371ec863a3e019ab0dbc4bb3b27e9077685aae4283ff6bbccfd981d92f9358f7efffbb72a940414802d98466d132e2ad0a16a12946d5f47', + 'b3606fe9b18c4aa7315e78bb9e47cb51cc4e203fcc2e631f0405c1b872c8e1cb5b6415ea74bbb77fffaaadb002b47cb4f4628dc0709634365b187667f5c708cb', + ].join(''), 'hex'); + + it('bytes', () => { + const rng = new LiteHashDRBG(fixedKey); + + let offset = 0; + assert.equalBytes(rng.nextBytes(10), testVector.slice(offset, offset + 10), '1'); + offset += 10; + assert.equalBytes(rng.nextBytes(32), testVector.slice(offset, offset + 32), '2'); + offset += 32; + assert.equalBytes(rng.nextBytes(1), testVector.slice(offset, offset + 1), '3'); + offset += 1; + assert.equalBytes(rng.nextBytes(64), testVector.slice(offset, offset + 64), '4'); + offset += 64; + assert.equalBytes(rng.nextBytes(5), testVector.slice(offset, offset + 5), '5'); + offset += 5; + + const lastA = rng.nextBytes(16, 18); + const lastB = new Uint8Array(18); + lastB.set(testVector.slice(offset, offset + 16)); + assert.equalBytes(lastA, lastB); + }); + + it('bits', () => { + const rng = new LiteHashDRBG(fixedKey); + + let offset = 0; + + const testBits = [10, 4, 15, 32, 7, 99, 512, 1, 2, 3]; + + for (const bits of testBits) { + const bytes = Math.ceil(bits / 8); + const A = rng.nextBits(bits); + const B = new Uint8Array(testVector.slice(offset, offset + bytes)); + offset += bytes; + + if (bits % 8 != 0) { + const bitMask = 0xff >> (8 - (bits % 8)); + B[bytes - 1] &= bitMask; + } + assert.equalBytes(A, B); + } + }); + + it('float', () => { + const rng = new LiteHashDRBG(fixedKey); + const rng2 = new DummyBlockRNG([ + new Uint8Array(testVector.slice(bufferByteSize * 0, bufferByteSize * 1)), + new Uint8Array(testVector.slice(bufferByteSize * 1, bufferByteSize * 2)), + new Uint8Array(testVector.slice(bufferByteSize * 2, bufferByteSize * 3)), + new Uint8Array(testVector.slice(bufferByteSize * 3, bufferByteSize * 4)), + new Uint8Array(testVector.slice(bufferByteSize * 4, bufferByteSize * 5)), + ]); + + for (const idx of _.range(18)) { + assert.equal(rng.nextFloat1(), rng2.nextFloat1(), `float${idx}`); + } + + }); +}); diff --git a/packages/logic/vitest.config.ts b/packages/logic/vitest.config.ts new file mode 100644 index 0000000..97cb2e9 --- /dev/null +++ b/packages/logic/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + globals: true, + include: ['test/**/*.test.ts'] + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cb263c..cf2e406 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,15 +27,467 @@ importers: packages/common: {} - packages/logic: {} + packages/logic: + dependencies: + js-sha512: + specifier: ^0.8.0 + version: 0.8.0 + devDependencies: + chai: + specifier: ^4.3.10 + version: 4.5.0 + chai-bytes: + specifier: ^0.1.2 + version: 0.1.2(chai@4.5.0) + lodash-es: + specifier: ^4.17.21 + version: 4.17.22 + vitest: + specifier: ^2.0.5 + version: 2.1.9(@types/node@20.19.27) tools/build-scripts: {} packages: + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/node@20.19.27': resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai-bytes@0.1.2: + resolution: {integrity: sha512-0ol6oJS0y1ozj6AZK8n1pyv1/G+l44nqUJygAkK1UrYl+IOGie5vcrEdrAlwmLYGIA9NVvtHWosPYwWWIXf/XA==} + engines: {node: '>=4'} + peerDependencies: + chai: '>=2 <5' + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + js-sha512@0.8.0: + resolution: {integrity: sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==} + + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -44,12 +496,487 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + snapshots: + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rollup/rollup-android-arm-eabi@4.54.0': + optional: true + + '@rollup/rollup-android-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-x64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.54.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.54.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.54.0': + optional: true + + '@types/estree@1.0.8': {} + '@types/node@20.19.27': dependencies: undici-types: 6.21.0 + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.27))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@20.19.27) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + assertion-error@1.1.0: {} + + assertion-error@2.0.1: {} + + cac@6.7.14: {} + + chai-bytes@0.1.2(chai@4.5.0): + dependencies: + chai: 4.5.0 + + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + check-error@2.1.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + + deep-eql@5.0.2: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + fsevents@2.3.3: + optional: true + + get-func-name@2.0.2: {} + + js-sha512@0.8.0: {} + + lodash-es@4.17.22: {} + + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + pathe@1.1.2: {} + + pathval@1.1.1: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.54.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 + fsevents: 2.3.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + type-detect@4.1.0: {} + typescript@5.9.3: {} undici-types@6.21.0: {} + + vite-node@2.1.9(@types/node@20.19.27): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@20.19.27) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@20.19.27): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.54.0 + optionalDependencies: + '@types/node': 20.19.27 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@20.19.27): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.27)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@20.19.27) + vite-node: 2.1.9(@types/node@20.19.27) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.27 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2