web based infinite canvas

feat: core math primitives

+2213 -26
+10 -2
.vscode/settings.json
··· 1 1 { 2 2 "[json]": { "editor.formatOnSave": true, "editor.defaultFormatter": "dprint.dprint" }, 3 3 "[jsonc]": { "editor.formatOnSave": true, "editor.defaultFormatter": "dprint.dprint" }, 4 - "[javascript]": { "editor.formatOnSave": true, "editor.defaultFormatter": "dprint.dprint" }, 5 - "[typescript]": { "editor.formatOnSave": true, "editor.defaultFormatter": "dprint.dprint" } 4 + "[javascript]": { 5 + "editor.formatOnSave": true, 6 + "editor.defaultFormatter": "dprint.dprint", 7 + "editor.codeActionsOnSave": { "source.fixAll.eslint": "always" } 8 + }, 9 + "[typescript]": { 10 + "editor.formatOnSave": true, 11 + "editor.defaultFormatter": "dprint.dprint", 12 + "editor.codeActionsOnSave": { "source.fixAll.eslint": "always" } 13 + } 6 14 }
+4 -6
TODO.txt
··· 47 47 48 48 Goal: a correct, testable camera transform (world <-> screen). 49 49 50 - Core primitives (/packages/core/src/math): 51 - [ ] Define Vec2 { x, y } + helpers: 50 + Core primitives (/packages/core/src/math.ts): 51 + [x] Define Vec2 { x, y } + helpers: 52 52 - add, sub, mulScalar, len, normalize, dot 53 - 54 - [ ] Define Box2 { min: Vec2, max: Vec2 }: 53 + [x] Define Box2 { min: Vec2, max: Vec2 }: 55 54 - fromPoints, containsPoint, intersectsBox 56 - 57 - [ ] Define Mat3 (2D affine) or equivalent: 55 + [x] Define Mat3 (2D affine) or equivalent: 58 56 - identity 59 57 - translate(tx, ty) 60 58 - scale(sx, sy)
+15
eslint.config.js
··· 1 + // @ts-check 2 + 3 + import eslint from "@eslint/js"; 4 + import eslintPluginUnicorn from "eslint-plugin-unicorn"; 5 + import { defineConfig } from "eslint/config"; 6 + import tseslint from "typescript-eslint"; 7 + 8 + export default defineConfig( 9 + eslint.configs.recommended, 10 + tseslint.configs.recommended, 11 + eslintPluginUnicorn.configs.recommended, 12 + [{ 13 + rules: { "unicorn/no-null": "off", "unicorn/prevent-abbreviations": ["error", { "replacements": { "i": false } }] }, 14 + }], 15 + );
+16 -3
package.json
··· 3 3 "version": "1.0.0", 4 4 "private": true, 5 5 "type": "module", 6 - "workspaces": ["packages/*"], 6 + "workspaces": [ 7 + "packages/*" 8 + ], 7 9 "scripts": {}, 8 - "devDependencies": { "dprint": "^0.50.2" }, 9 - "engines": { "node": ">=18.0.0", "pnpm": ">=8.0.0" }, 10 + "devDependencies": { 11 + "@eslint/js": "^9.39.2", 12 + "dprint": "^0.50.2", 13 + "eslint": "^9.39.2", 14 + "eslint-plugin-unicorn": "^62.0.0", 15 + "globals": "^16.5.0", 16 + "typescript": "^5.9.3", 17 + "typescript-eslint": "^8.50.0" 18 + }, 19 + "engines": { 20 + "node": ">=18.0.0", 21 + "pnpm": ">=8.0.0" 22 + }, 10 23 "packageManager": "pnpm@10.26.1" 11 24 }
+5 -15
packages/core/package.json
··· 1 1 { 2 - "name": "tsdown-starter", 2 + "name": "inkfinite-core", 3 3 "type": "module", 4 4 "version": "0.0.0", 5 5 "description": "A starter for creating a TypeScript package.", 6 6 "author": "Author Name <author.name@mail.com>", 7 7 "license": "MIT", 8 8 "homepage": "https://github.com/author/library#readme", 9 - "repository": { 10 - "type": "git", 11 - "url": "git+https://github.com/author/library.git" 12 - }, 13 - "bugs": { 14 - "url": "https://github.com/author/library/issues" 15 - }, 16 - "exports": { 17 - ".": "./dist/index.mjs", 18 - "./package.json": "./package.json" 19 - }, 9 + "repository": { "type": "git", "url": "git+https://github.com/author/library.git" }, 10 + "bugs": { "url": "https://github.com/author/library/issues" }, 11 + "exports": { ".": "./dist/index.mjs", "./package.json": "./package.json" }, 20 12 "main": "./dist/index.mjs", 21 13 "module": "./dist/index.mjs", 22 14 "types": "./dist/index.d.mts", 23 - "files": [ 24 - "dist" 25 - ], 15 + "files": ["dist"], 26 16 "scripts": { 27 17 "build": "tsdown", 28 18 "dev": "tsdown --watch",
+360
packages/core/src/math.ts
··· 1 + export type Vec2 = { x: number; y: number }; 2 + 3 + export const Vec2 = { 4 + /** 5 + * Add two vectors 6 + */ 7 + add(a: Vec2, b: Vec2): Vec2 { 8 + return { x: a.x + b.x, y: a.y + b.y }; 9 + }, 10 + 11 + /** 12 + * Subtract vector b from vector a 13 + */ 14 + sub(a: Vec2, b: Vec2): Vec2 { 15 + return { x: a.x - b.x, y: a.y - b.y }; 16 + }, 17 + 18 + /** 19 + * Multiply vector by scalar 20 + */ 21 + mulScalar(v: Vec2, s: number): Vec2 { 22 + return { x: v.x * s, y: v.y * s }; 23 + }, 24 + 25 + /** 26 + * Calculate length (magnitude) of vector 27 + */ 28 + len(v: Vec2): number { 29 + return Math.hypot(v.x, v.y); 30 + }, 31 + 32 + /** 33 + * Calculate squared length (faster, no sqrt) 34 + */ 35 + lenSq(v: Vec2): number { 36 + return v.x * v.x + v.y * v.y; 37 + }, 38 + 39 + /** 40 + * Normalize vector to unit length 41 + * Returns zero vector if input length is zero 42 + */ 43 + normalize(v: Vec2): Vec2 { 44 + const length = Vec2.len(v); 45 + if (length === 0) { 46 + return { x: 0, y: 0 }; 47 + } 48 + return { x: v.x / length, y: v.y / length }; 49 + }, 50 + 51 + /** 52 + * Calculate dot product of two vectors 53 + */ 54 + dot(a: Vec2, b: Vec2): number { 55 + return a.x * b.x + a.y * b.y; 56 + }, 57 + 58 + /** 59 + * Calculate distance between two points 60 + */ 61 + dist(a: Vec2, b: Vec2): number { 62 + return Vec2.len(Vec2.sub(a, b)); 63 + }, 64 + 65 + /** 66 + * Calculate squared distance (faster, no sqrt) 67 + */ 68 + distSq(a: Vec2, b: Vec2): number { 69 + return Vec2.lenSq(Vec2.sub(a, b)); 70 + }, 71 + 72 + /** 73 + * Check if two vectors are approximately equal 74 + */ 75 + equals(a: Vec2, b: Vec2, epsilon = 1e-10): boolean { 76 + return Math.abs(a.x - b.x) <= epsilon && Math.abs(a.y - b.y) <= epsilon; 77 + }, 78 + 79 + /** 80 + * Create a new vector 81 + */ 82 + create(x: number, y: number): Vec2 { 83 + return { x, y }; 84 + }, 85 + 86 + /** 87 + * Clone a vector 88 + */ 89 + clone(v: Vec2): Vec2 { 90 + return { x: v.x, y: v.y }; 91 + }, 92 + }; 93 + 94 + export type Box2 = { min: Vec2; max: Vec2 }; 95 + 96 + export const Box2 = { 97 + /** 98 + * Create a bounding box from an array of points 99 + */ 100 + fromPoints(points: Vec2[]): Box2 { 101 + if (points.length === 0) { 102 + return { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }; 103 + } 104 + 105 + let minX = points[0].x; 106 + let minY = points[0].y; 107 + let maxX = points[0].x; 108 + let maxY = points[0].y; 109 + 110 + for (let index = 1; index < points.length; index++) { 111 + const p = points[index]; 112 + if (p.x < minX) minX = p.x; 113 + if (p.y < minY) minY = p.y; 114 + if (p.x > maxX) maxX = p.x; 115 + if (p.y > maxY) maxY = p.y; 116 + } 117 + 118 + return { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } }; 119 + }, 120 + 121 + /** 122 + * Create a box from center and size 123 + */ 124 + fromCenterSize(center: Vec2, width: number, height: number): Box2 { 125 + const halfW = width / 2; 126 + const halfH = height / 2; 127 + return { min: { x: center.x - halfW, y: center.y - halfH }, max: { x: center.x + halfW, y: center.y + halfH } }; 128 + }, 129 + 130 + /** 131 + * Create a box from min/max coordinates 132 + */ 133 + create(minX: number, minY: number, maxX: number, maxY: number): Box2 { 134 + return { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } }; 135 + }, 136 + 137 + /** 138 + * Check if a point is inside the box 139 + */ 140 + containsPoint(box: Box2, point: Vec2): boolean { 141 + return (point.x >= box.min.x && point.x <= box.max.x && point.y >= box.min.y && point.y <= box.max.y); 142 + }, 143 + 144 + /** 145 + * Check if two boxes intersect 146 + */ 147 + intersectsBox(a: Box2, b: Box2): boolean { 148 + return !(a.max.x < b.min.x || a.min.x > b.max.x || a.max.y < b.min.y || a.min.y > b.max.y); 149 + }, 150 + 151 + /** 152 + * Check if box a completely contains box b 153 + */ 154 + containsBox(a: Box2, b: Box2): boolean { 155 + return (b.min.x >= a.min.x && b.max.x <= a.max.x && b.min.y >= a.min.y && b.max.y <= a.max.y); 156 + }, 157 + 158 + /** 159 + * Get the width of the box 160 + */ 161 + width(box: Box2): number { 162 + return box.max.x - box.min.x; 163 + }, 164 + 165 + /** 166 + * Get the height of the box 167 + */ 168 + height(box: Box2): number { 169 + return box.max.y - box.min.y; 170 + }, 171 + 172 + /** 173 + * Get the center point of the box 174 + */ 175 + center(box: Box2): Vec2 { 176 + return { x: (box.min.x + box.max.x) / 2, y: (box.min.y + box.max.y) / 2 }; 177 + }, 178 + 179 + /** 180 + * Get the area of the box 181 + */ 182 + area(box: Box2): number { 183 + return Box2.width(box) * Box2.height(box); 184 + }, 185 + 186 + /** 187 + * Expand box to include a point 188 + */ 189 + expandToPoint(box: Box2, point: Vec2): Box2 { 190 + return { 191 + min: { x: Math.min(box.min.x, point.x), y: Math.min(box.min.y, point.y) }, 192 + max: { x: Math.max(box.max.x, point.x), y: Math.max(box.max.y, point.y) }, 193 + }; 194 + }, 195 + 196 + /** 197 + * Clone a box 198 + */ 199 + clone(box: Box2): Box2 { 200 + return { min: { ...box.min }, max: { ...box.max } }; 201 + }, 202 + }; 203 + 204 + /** 205 + * 3x3 matrix stored in column-major order for 2D affine transforms 206 + * Layout: 207 + * [a c tx] 208 + * [b d ty] 209 + * [0 0 1] 210 + * 211 + * Stored as: [a, b, 0, c, d, 0, tx, ty, 1] 212 + */ 213 + export type Mat3 = [number, number, number, number, number, number, number, number, number]; 214 + 215 + export const Mat3 = { 216 + /** 217 + * Create an identity matrix 218 + */ 219 + identity(): Mat3 { 220 + return [1, 0, 0, 0, 1, 0, 0, 0, 1]; 221 + }, 222 + 223 + /** 224 + * Create a translation matrix 225 + */ 226 + translate(tx: number, ty: number): Mat3 { 227 + return [1, 0, 0, 0, 1, 0, tx, ty, 1]; 228 + }, 229 + 230 + /** 231 + * Create a scale matrix 232 + */ 233 + scale(sx: number, sy: number): Mat3 { 234 + return [sx, 0, 0, 0, sy, 0, 0, 0, 1]; 235 + }, 236 + 237 + /** 238 + * Create a rotation matrix 239 + * @param theta - angle in radians 240 + */ 241 + rotate(theta: number): Mat3 { 242 + const c = Math.cos(theta); 243 + const s = Math.sin(theta); 244 + return [c, s, 0, -s, c, 0, 0, 0, 1]; 245 + }, 246 + 247 + /** 248 + * Multiply two matrices: result = a * b 249 + * Order matters: transformations are applied right to left 250 + */ 251 + multiply(a: Mat3, b: Mat3): Mat3 { 252 + const a00 = a[0], a01 = a[1], a02 = a[2]; 253 + const a10 = a[3], a11 = a[4], a12 = a[5]; 254 + const a20 = a[6], a21 = a[7], a22 = a[8]; 255 + 256 + const b00 = b[0], b01 = b[1], b02 = b[2]; 257 + const b10 = b[3], b11 = b[4], b12 = b[5]; 258 + const b20 = b[6], b21 = b[7], b22 = b[8]; 259 + 260 + return [ 261 + a00 * b00 + a10 * b01 + a20 * b02, 262 + a01 * b00 + a11 * b01 + a21 * b02, 263 + a02 * b00 + a12 * b01 + a22 * b02, 264 + 265 + a00 * b10 + a10 * b11 + a20 * b12, 266 + a01 * b10 + a11 * b11 + a21 * b12, 267 + a02 * b10 + a12 * b11 + a22 * b12, 268 + 269 + a00 * b20 + a10 * b21 + a20 * b22, 270 + a01 * b20 + a11 * b21 + a21 * b22, 271 + a02 * b20 + a12 * b21 + a22 * b22, 272 + ]; 273 + }, 274 + 275 + /** 276 + * Transform a point by a matrix 277 + */ 278 + transformPoint(m: Mat3, p: Vec2): Vec2 { 279 + const x = m[0] * p.x + m[3] * p.y + m[6]; 280 + const y = m[1] * p.x + m[4] * p.y + m[7]; 281 + return { x, y }; 282 + }, 283 + 284 + /** 285 + * Invert a matrix 286 + * Returns null if matrix is not invertible 287 + */ 288 + invert(m: Mat3): Mat3 | null { 289 + const a00 = m[0], a01 = m[1], a02 = m[2]; 290 + const a10 = m[3], a11 = m[4], a12 = m[5]; 291 + const a20 = m[6], a21 = m[7], a22 = m[8]; 292 + 293 + const b01 = a22 * a11 - a12 * a21; 294 + const b11 = -a22 * a10 + a12 * a20; 295 + const b21 = a21 * a10 - a11 * a20; 296 + 297 + const det = a00 * b01 + a01 * b11 + a02 * b21; 298 + 299 + if (Math.abs(det) < 1e-10) { 300 + return null; 301 + } 302 + 303 + const invDet = 1 / det; 304 + 305 + return [ 306 + b01 * invDet, 307 + (-a22 * a01 + a02 * a21) * invDet, 308 + (a12 * a01 - a02 * a11) * invDet, 309 + 310 + b11 * invDet, 311 + (a22 * a00 - a02 * a20) * invDet, 312 + (-a12 * a00 + a02 * a10) * invDet, 313 + 314 + b21 * invDet, 315 + (-a21 * a00 + a01 * a20) * invDet, 316 + (a11 * a00 - a01 * a10) * invDet, 317 + ]; 318 + }, 319 + 320 + /** 321 + * Get the determinant of a matrix 322 + */ 323 + determinant(m: Mat3): number { 324 + const a00 = m[0], a01 = m[1], a02 = m[2]; 325 + const a10 = m[3], a11 = m[4], a12 = m[5]; 326 + const a20 = m[6], a21 = m[7], a22 = m[8]; 327 + 328 + return (a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20)); 329 + }, 330 + 331 + /** 332 + * Clone a matrix 333 + */ 334 + clone(m: Mat3): Mat3 { 335 + return [...m] as Mat3; 336 + }, 337 + 338 + /** 339 + * Check if two matrices are approximately equal 340 + */ 341 + equals(a: Mat3, b: Mat3, epsilon = 1e-10): boolean { 342 + for (let index = 0; index < 9; index++) { 343 + if (Math.abs(a[index] - b[index]) >= epsilon) { 344 + return false; 345 + } 346 + } 347 + return true; 348 + }, 349 + 350 + /** 351 + * Create a combined transform matrix 352 + * Applies in order: translate -> rotate -> scale 353 + */ 354 + fromTransform(tx: number, ty: number, rotation: number, sx: number, sy: number): Mat3 { 355 + const c = Math.cos(rotation); 356 + const s = Math.sin(rotation); 357 + 358 + return [c * sx, s * sx, 0, -s * sy, c * sy, 0, tx, ty, 1]; 359 + }, 360 + };
+770
packages/core/tests/math.test.ts
··· 1 + import { describe, expect, it } from "vitest"; 2 + import { Box2, Mat3, Vec2 } from "../src/math"; 3 + 4 + describe("Vec2", () => { 5 + describe("add", () => { 6 + it.each([{ 7 + description: "should add two positive vectors", 8 + a: { x: 1, y: 2 }, 9 + b: { x: 3, y: 4 }, 10 + expected: { x: 4, y: 6 }, 11 + }, { 12 + description: "should handle negative values", 13 + a: { x: -1, y: -2 }, 14 + b: { x: 3, y: 4 }, 15 + expected: { x: 2, y: 2 }, 16 + }, { 17 + description: "should handle zero vectors", 18 + a: { x: 0, y: 0 }, 19 + b: { x: 5, y: 10 }, 20 + expected: { x: 5, y: 10 }, 21 + }])("$description", ({ a, b, expected }) => { 22 + expect(Vec2.add(a, b)).toEqual(expected); 23 + }); 24 + 25 + it("should be commutative", () => { 26 + const a = { x: 1, y: 2 }; 27 + const b = { x: 3, y: 4 }; 28 + expect(Vec2.add(a, b)).toEqual(Vec2.add(b, a)); 29 + }); 30 + }); 31 + 32 + describe("sub", () => { 33 + it.each([{ 34 + description: "should subtract two vectors", 35 + a: { x: 5, y: 7 }, 36 + b: { x: 2, y: 3 }, 37 + expected: { x: 3, y: 4 }, 38 + }, { 39 + description: "should handle negative results", 40 + a: { x: 1, y: 2 }, 41 + b: { x: 3, y: 4 }, 42 + expected: { x: -2, y: -2 }, 43 + }, { 44 + description: "should handle zero vectors", 45 + a: { x: 5, y: 10 }, 46 + b: { x: 0, y: 0 }, 47 + expected: { x: 5, y: 10 }, 48 + }])("$description", ({ a, b, expected }) => { 49 + expect(Vec2.sub(a, b)).toEqual(expected); 50 + }); 51 + 52 + it("should return zero vector when subtracting identical vectors", () => { 53 + const a = { x: 5, y: 10 }; 54 + expect(Vec2.sub(a, a)).toEqual({ x: 0, y: 0 }); 55 + }); 56 + }); 57 + 58 + describe("mulScalar", () => { 59 + it.each([ 60 + { description: "should multiply by positive scalar", v: { x: 2, y: 3 }, scalar: 4, expected: { x: 8, y: 12 } }, 61 + { description: "should multiply by negative scalar", v: { x: 2, y: 3 }, scalar: -2, expected: { x: -4, y: -6 } }, 62 + { description: "should multiply by zero", v: { x: 5, y: 10 }, scalar: 0, expected: { x: 0, y: 0 } }, 63 + { description: "should multiply by one (identity)", v: { x: 5, y: 10 }, scalar: 1, expected: { x: 5, y: 10 } }, 64 + { description: "should handle fractional scalars", v: { x: 10, y: 20 }, scalar: 0.5, expected: { x: 5, y: 10 } }, 65 + ])("$description", ({ v, scalar, expected }) => { 66 + expect(Vec2.mulScalar(v, scalar)).toEqual(expected); 67 + }); 68 + }); 69 + 70 + describe("len", () => { 71 + it.each([ 72 + { description: "should calculate length of 3-4-5 triangle", v: { x: 3, y: 4 }, expected: 5 }, 73 + { description: "should return zero for zero vector", v: { x: 0, y: 0 }, expected: 0 }, 74 + { description: "should handle negative components", v: { x: -3, y: -4 }, expected: 5 }, 75 + { description: "should calculate length for unit vector X", v: { x: 1, y: 0 }, expected: 1 }, 76 + { description: "should calculate length for unit vector Y", v: { x: 0, y: 1 }, expected: 1 }, 77 + ])("$description", ({ v, expected }) => { 78 + expect(Vec2.len(v)).toBe(expected); 79 + }); 80 + 81 + it("should handle very small vectors", () => { 82 + const v = { x: 1e-10, y: 1e-10 }; 83 + expect(Vec2.len(v)).toBeCloseTo(Math.sqrt(2) * 1e-10, 20); 84 + }); 85 + }); 86 + 87 + describe("lenSq", () => { 88 + it.each([{ description: "should calculate squared length", v: { x: 3, y: 4 }, expected: 25 }, { 89 + description: "should return zero for zero vector", 90 + v: { x: 0, y: 0 }, 91 + expected: 0, 92 + }])("$description", ({ v, expected }) => { 93 + expect(Vec2.lenSq(v)).toBe(expected); 94 + }); 95 + 96 + it("should match len squared", () => { 97 + const v = { x: 3, y: 4 }; 98 + expect(Vec2.lenSq(v)).toBe(Vec2.len(v) ** 2); 99 + }); 100 + }); 101 + 102 + describe("normalize", () => { 103 + it("should normalize vector to unit length", () => { 104 + const v = { x: 3, y: 4 }; 105 + const result = Vec2.normalize(v); 106 + expect(result.x).toBeCloseTo(0.6); 107 + expect(result.y).toBeCloseTo(0.8); 108 + expect(Vec2.len(result)).toBeCloseTo(1); 109 + }); 110 + 111 + it("should handle zero vector (return zero)", () => { 112 + const v = { x: 0, y: 0 }; 113 + expect(Vec2.normalize(v)).toEqual({ x: 0, y: 0 }); 114 + }); 115 + 116 + it("should handle already normalized vectors", () => { 117 + const v = { x: 1, y: 0 }; 118 + const result = Vec2.normalize(v); 119 + expect(result.x).toBeCloseTo(1); 120 + expect(result.y).toBeCloseTo(0); 121 + }); 122 + 123 + it("should handle negative components", () => { 124 + const v = { x: -3, y: -4 }; 125 + const result = Vec2.normalize(v); 126 + expect(result.x).toBeCloseTo(-0.6); 127 + expect(result.y).toBeCloseTo(-0.8); 128 + expect(Vec2.len(result)).toBeCloseTo(1); 129 + }); 130 + 131 + it("should handle very small vectors", () => { 132 + const v = { x: 1e-100, y: 1e-100 }; 133 + const result = Vec2.normalize(v); 134 + const expectedX = 1 / Math.sqrt(2); 135 + expect(result.x).toBeCloseTo(expectedX, 5); 136 + expect(result.y).toBeCloseTo(expectedX, 5); 137 + }); 138 + }); 139 + 140 + describe("dot", () => { 141 + it.each([ 142 + { description: "should calculate dot product", a: { x: 2, y: 3 }, b: { x: 4, y: 5 }, expected: 23 }, 143 + { 144 + description: "should return zero for perpendicular vectors", 145 + a: { x: 1, y: 0 }, 146 + b: { x: 0, y: 1 }, 147 + expected: 0, 148 + }, 149 + { description: "should handle negative values", a: { x: 2, y: 3 }, b: { x: -4, y: -5 }, expected: -23 }, 150 + { description: "should return zero with zero vector", a: { x: 5, y: 10 }, b: { x: 0, y: 0 }, expected: 0 }, 151 + { 152 + description: "should calculate dot product of parallel vectors", 153 + a: { x: 2, y: 3 }, 154 + b: { x: 4, y: 6 }, 155 + expected: 26, 156 + }, 157 + ])("$description", ({ a, b, expected }) => { 158 + expect(Vec2.dot(a, b)).toBe(expected); 159 + }); 160 + 161 + it("should be commutative", () => { 162 + const a = { x: 2, y: 3 }; 163 + const b = { x: 4, y: 5 }; 164 + expect(Vec2.dot(a, b)).toBe(Vec2.dot(b, a)); 165 + }); 166 + }); 167 + 168 + describe("dist and distSq", () => { 169 + it.each([{ 170 + description: "should calculate distance between two points", 171 + a: { x: 0, y: 0 }, 172 + b: { x: 3, y: 4 }, 173 + expectedDist: 5, 174 + expectedDistSq: 25, 175 + }, { 176 + description: "should return zero for identical points", 177 + a: { x: 5, y: 10 }, 178 + b: { x: 5, y: 10 }, 179 + expectedDist: 0, 180 + expectedDistSq: 0, 181 + }, { 182 + description: "should handle negative coordinates", 183 + a: { x: -3, y: -4 }, 184 + b: { x: 0, y: 0 }, 185 + expectedDist: 5, 186 + expectedDistSq: 25, 187 + }])("$description", ({ a, b, expectedDist, expectedDistSq }) => { 188 + expect(Vec2.dist(a, b)).toBe(expectedDist); 189 + expect(Vec2.distSq(a, b)).toBe(expectedDistSq); 190 + }); 191 + 192 + it("should be symmetric", () => { 193 + const a = { x: 1, y: 2 }; 194 + const b = { x: 4, y: 6 }; 195 + expect(Vec2.dist(a, b)).toBe(Vec2.dist(b, a)); 196 + }); 197 + 198 + it("distSq should match dist squared", () => { 199 + const a = { x: 1, y: 2 }; 200 + const b = { x: 4, y: 6 }; 201 + expect(Vec2.distSq(a, b)).toBe(Vec2.dist(a, b) ** 2); 202 + }); 203 + }); 204 + 205 + describe("equals", () => { 206 + it.each([{ 207 + description: "should return true for identical vectors", 208 + a: { x: 1.5, y: 2.5 }, 209 + b: { x: 1.5, y: 2.5 }, 210 + expected: true, 211 + }, { 212 + description: "should return false for different vectors", 213 + a: { x: 1, y: 2 }, 214 + b: { x: 3, y: 4 }, 215 + expected: false, 216 + }])("$description", ({ a, b, expected }) => { 217 + expect(Vec2.equals(a, b)).toBe(expected); 218 + }); 219 + 220 + it("should use epsilon for floating point comparison", () => { 221 + const a = { x: 1 + 5e-11, y: 2 + 5e-11 }; 222 + const b = { x: 1, y: 2 }; 223 + expect(Vec2.equals(a, b)).toBe(true); 224 + }); 225 + 226 + it("should allow custom epsilon", () => { 227 + const a = { x: 1.001, y: 2.001 }; 228 + const b = { x: 1, y: 2 }; 229 + expect(Vec2.equals(a, b, 0.01)).toBe(true); 230 + expect(Vec2.equals(a, b, 0.0001)).toBe(false); 231 + }); 232 + }); 233 + 234 + describe("create and clone", () => { 235 + it("should create a vector", () => { 236 + expect(Vec2.create(3, 4)).toEqual({ x: 3, y: 4 }); 237 + }); 238 + 239 + it("should clone a vector", () => { 240 + const original = { x: 5, y: 10 }; 241 + const cloned = Vec2.clone(original); 242 + expect(cloned).toEqual(original); 243 + expect(cloned).not.toBe(original); 244 + }); 245 + }); 246 + }); 247 + 248 + describe("Box2", () => { 249 + describe("fromPoints", () => { 250 + it.each([ 251 + { 252 + description: "should create box from multiple points", 253 + points: [{ x: 1, y: 2 }, { x: 5, y: 8 }, { x: 3, y: 4 }], 254 + expected: { min: { x: 1, y: 2 }, max: { x: 5, y: 8 } }, 255 + }, 256 + { 257 + description: "should handle single point", 258 + points: [{ x: 3, y: 4 }], 259 + expected: { min: { x: 3, y: 4 }, max: { x: 3, y: 4 } }, 260 + }, 261 + { description: "should handle empty array", points: [], expected: { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } } }, 262 + { 263 + description: "should handle negative coordinates", 264 + points: [{ x: -5, y: -3 }, { x: 2, y: 4 }, { x: -1, y: 0 }], 265 + expected: { min: { x: -5, y: -3 }, max: { x: 2, y: 4 } }, 266 + }, 267 + ])("$description", ({ points, expected }) => { 268 + expect(Box2.fromPoints(points)).toEqual(expected); 269 + }); 270 + 271 + it("should handle points in any order", () => { 272 + const points1 = [{ x: 0, y: 0 }, { x: 10, y: 10 }]; 273 + const points2 = [{ x: 10, y: 10 }, { x: 0, y: 0 }]; 274 + expect(Box2.fromPoints(points1)).toEqual(Box2.fromPoints(points2)); 275 + }); 276 + }); 277 + 278 + describe("fromCenterSize", () => { 279 + it("should create box from center and size", () => { 280 + const center = { x: 5, y: 5 }; 281 + const box = Box2.fromCenterSize(center, 10, 6); 282 + expect(box).toEqual({ min: { x: 0, y: 2 }, max: { x: 10, y: 8 } }); 283 + }); 284 + 285 + it("should handle zero size", () => { 286 + const center = { x: 5, y: 5 }; 287 + const box = Box2.fromCenterSize(center, 0, 0); 288 + expect(box).toEqual({ min: { x: 5, y: 5 }, max: { x: 5, y: 5 } }); 289 + }); 290 + 291 + it("should handle odd dimensions", () => { 292 + const center = { x: 0, y: 0 }; 293 + const box = Box2.fromCenterSize(center, 5, 3); 294 + expect(box.min.x).toBeCloseTo(-2.5); 295 + expect(box.min.y).toBeCloseTo(-1.5); 296 + expect(box.max.x).toBeCloseTo(2.5); 297 + expect(box.max.y).toBeCloseTo(1.5); 298 + }); 299 + }); 300 + 301 + describe("create", () => { 302 + it("should create box from coordinates", () => { 303 + expect(Box2.create(0, 0, 10, 10)).toEqual({ min: { x: 0, y: 0 }, max: { x: 10, y: 10 } }); 304 + }); 305 + }); 306 + 307 + describe("containsPoint", () => { 308 + const box = Box2.create(0, 0, 10, 10); 309 + 310 + it.each([ 311 + { description: "point inside box", point: { x: 5, y: 5 }, expected: true }, 312 + { description: "point on left edge", point: { x: 0, y: 5 }, expected: true }, 313 + { description: "point on right edge", point: { x: 10, y: 5 }, expected: true }, 314 + { description: "point on top edge", point: { x: 5, y: 0 }, expected: true }, 315 + { description: "point on bottom edge", point: { x: 5, y: 10 }, expected: true }, 316 + { description: "top-left corner", point: { x: 0, y: 0 }, expected: true }, 317 + { description: "bottom-right corner", point: { x: 10, y: 10 }, expected: true }, 318 + { description: "point left of box", point: { x: -1, y: 5 }, expected: false }, 319 + { description: "point right of box", point: { x: 11, y: 5 }, expected: false }, 320 + { description: "point above box", point: { x: 5, y: -1 }, expected: false }, 321 + { description: "point below box", point: { x: 5, y: 11 }, expected: false }, 322 + ])("should handle $description", ({ point, expected }) => { 323 + expect(Box2.containsPoint(box, point)).toBe(expected); 324 + }); 325 + 326 + it("should handle negative coordinates", () => { 327 + const negBox = Box2.create(-10, -10, 10, 10); 328 + expect(Box2.containsPoint(negBox, { x: 0, y: 0 })).toBe(true); 329 + expect(Box2.containsPoint(negBox, { x: -5, y: -5 })).toBe(true); 330 + expect(Box2.containsPoint(negBox, { x: -11, y: 0 })).toBe(false); 331 + }); 332 + 333 + it("should handle zero-size box", () => { 334 + const pointBox = Box2.create(5, 5, 5, 5); 335 + expect(Box2.containsPoint(pointBox, { x: 5, y: 5 })).toBe(true); 336 + expect(Box2.containsPoint(pointBox, { x: 5.1, y: 5 })).toBe(false); 337 + }); 338 + }); 339 + 340 + describe("intersectsBox", () => { 341 + it.each([{ 342 + description: "overlapping boxes", 343 + a: Box2.create(0, 0, 10, 10), 344 + b: Box2.create(5, 5, 15, 15), 345 + expected: true, 346 + }, { 347 + description: "boxes touching at edge", 348 + a: Box2.create(0, 0, 10, 10), 349 + b: Box2.create(10, 0, 20, 10), 350 + expected: true, 351 + }, { 352 + description: "one box contains another", 353 + a: Box2.create(0, 0, 10, 10), 354 + b: Box2.create(2, 2, 8, 8), 355 + expected: true, 356 + }, { 357 + description: "non-overlapping boxes", 358 + a: Box2.create(0, 0, 10, 10), 359 + b: Box2.create(11, 11, 20, 20), 360 + expected: false, 361 + }, { 362 + description: "boxes separated horizontally", 363 + a: Box2.create(0, 0, 10, 10), 364 + b: Box2.create(11, 0, 20, 10), 365 + expected: false, 366 + }, { 367 + description: "boxes separated vertically", 368 + a: Box2.create(0, 0, 10, 10), 369 + b: Box2.create(0, 11, 10, 20), 370 + expected: false, 371 + }])("should handle $description", ({ a, b, expected }) => { 372 + expect(Box2.intersectsBox(a, b)).toBe(expected); 373 + }); 374 + 375 + it("should handle negative coordinates", () => { 376 + const a = Box2.create(-10, -10, 0, 0); 377 + const b = Box2.create(-5, -5, 5, 5); 378 + expect(Box2.intersectsBox(a, b)).toBe(true); 379 + }); 380 + 381 + it("should be symmetric", () => { 382 + const a = Box2.create(0, 0, 10, 10); 383 + const b = Box2.create(5, 5, 15, 15); 384 + expect(Box2.intersectsBox(a, b)).toBe(Box2.intersectsBox(b, a)); 385 + }); 386 + }); 387 + 388 + describe("containsBox", () => { 389 + it.each([ 390 + { description: "a contains b", a: Box2.create(0, 0, 10, 10), b: Box2.create(2, 2, 8, 8), expected: true }, 391 + { 392 + description: "b contains a (should be false)", 393 + a: Box2.create(2, 2, 8, 8), 394 + b: Box2.create(0, 0, 10, 10), 395 + expected: false, 396 + }, 397 + { description: "identical boxes", a: Box2.create(0, 0, 10, 10), b: Box2.create(0, 0, 10, 10), expected: true }, 398 + { 399 + description: "overlapping but not contained", 400 + a: Box2.create(0, 0, 10, 10), 401 + b: Box2.create(5, 5, 15, 15), 402 + expected: false, 403 + }, 404 + { 405 + description: "boxes just touching", 406 + a: Box2.create(0, 0, 10, 10), 407 + b: Box2.create(10, 0, 20, 10), 408 + expected: false, 409 + }, 410 + ])("should handle $description", ({ a, b, expected }) => { 411 + expect(Box2.containsBox(a, b)).toBe(expected); 412 + }); 413 + }); 414 + 415 + describe("width, height, center, area", () => { 416 + it.each([{ 417 + description: "standard box", 418 + box: Box2.create(0, 0, 10, 5), 419 + width: 10, 420 + height: 5, 421 + center: { x: 5, y: 2.5 }, 422 + area: 50, 423 + }, { 424 + description: "zero-width box", 425 + box: Box2.create(5, 5, 5, 10), 426 + width: 0, 427 + height: 5, 428 + center: { x: 5, y: 7.5 }, 429 + area: 0, 430 + }, { 431 + description: "negative coordinate box", 432 + box: Box2.create(-10, -5, 10, 5), 433 + width: 20, 434 + height: 10, 435 + center: { x: 0, y: 0 }, 436 + area: 200, 437 + }, { 438 + description: "zero-size box", 439 + box: Box2.create(5, 5, 5, 5), 440 + width: 0, 441 + height: 0, 442 + center: { x: 5, y: 5 }, 443 + area: 0, 444 + }])("should calculate properties for $description", ({ box, width, height, center, area }) => { 445 + expect(Box2.width(box)).toBe(width); 446 + expect(Box2.height(box)).toBe(height); 447 + expect(Box2.center(box)).toEqual(center); 448 + expect(Box2.area(box)).toBe(area); 449 + }); 450 + }); 451 + 452 + describe("expandToPoint", () => { 453 + it.each([{ 454 + description: "expand to point outside (right)", 455 + box: Box2.create(0, 0, 10, 10), 456 + point: { x: 15, y: 5 }, 457 + expected: { min: { x: 0, y: 0 }, max: { x: 15, y: 10 } }, 458 + }, { 459 + description: "point inside (no change)", 460 + box: Box2.create(0, 0, 10, 10), 461 + point: { x: 5, y: 5 }, 462 + expected: { min: { x: 0, y: 0 }, max: { x: 10, y: 10 } }, 463 + }, { 464 + description: "expand in multiple directions", 465 + box: Box2.create(5, 5, 10, 10), 466 + point: { x: 0, y: 15 }, 467 + expected: { min: { x: 0, y: 5 }, max: { x: 10, y: 15 } }, 468 + }, { 469 + description: "expand to negative coordinates", 470 + box: Box2.create(0, 0, 10, 10), 471 + point: { x: -5, y: -5 }, 472 + expected: { min: { x: -5, y: -5 }, max: { x: 10, y: 10 } }, 473 + }])("should $description", ({ box, point, expected }) => { 474 + expect(Box2.expandToPoint(box, point)).toEqual(expected); 475 + }); 476 + }); 477 + 478 + describe("clone", () => { 479 + it("should create a copy of the box", () => { 480 + const box = Box2.create(0, 0, 10, 10); 481 + const cloned = Box2.clone(box); 482 + expect(cloned).toEqual(box); 483 + expect(cloned).not.toBe(box); 484 + expect(cloned.min).not.toBe(box.min); 485 + expect(cloned.max).not.toBe(box.max); 486 + }); 487 + }); 488 + }); 489 + 490 + describe("Mat3", () => { 491 + describe("identity", () => { 492 + it("should create identity matrix", () => { 493 + expect(Mat3.identity()).toEqual([1, 0, 0, 0, 1, 0, 0, 0, 1]); 494 + }); 495 + 496 + it("should not transform points", () => { 497 + const m = Mat3.identity(); 498 + const p = { x: 5, y: 10 }; 499 + expect(Mat3.transformPoint(m, p)).toEqual(p); 500 + }); 501 + }); 502 + 503 + describe("translate", () => { 504 + it.each([{ description: "positive translation", tx: 5, ty: 10, point: { x: 0, y: 0 }, expected: { x: 5, y: 10 } }, { 505 + description: "negative translation", 506 + tx: -5, 507 + ty: -10, 508 + point: { x: 5, y: 10 }, 509 + expected: { x: 0, y: 0 }, 510 + }, { 511 + description: "zero translation (identity)", 512 + tx: 0, 513 + ty: 0, 514 + point: { x: 5, y: 10 }, 515 + expected: { x: 5, y: 10 }, 516 + }])("should handle $description", ({ tx, ty, point, expected }) => { 517 + const m = Mat3.translate(tx, ty); 518 + expect(Mat3.transformPoint(m, point)).toEqual(expected); 519 + }); 520 + 521 + it("should create correct matrix structure", () => { 522 + expect(Mat3.translate(5, 10)).toEqual([1, 0, 0, 0, 1, 0, 5, 10, 1]); 523 + }); 524 + }); 525 + 526 + describe("scale", () => { 527 + it.each([ 528 + { description: "non-uniform scale", sx: 2, sy: 3, point: { x: 5, y: 10 }, expected: { x: 10, y: 30 } }, 529 + { description: "uniform scale", sx: 2, sy: 2, point: { x: 5, y: 10 }, expected: { x: 10, y: 20 } }, 530 + { description: "scale by 1 (identity)", sx: 1, sy: 1, point: { x: 5, y: 10 }, expected: { x: 5, y: 10 } }, 531 + { description: "scale by 0 (collapse)", sx: 0, sy: 0, point: { x: 5, y: 10 }, expected: { x: 0, y: 0 } }, 532 + ])("should handle $description", ({ sx, sy, point, expected }) => { 533 + const m = Mat3.scale(sx, sy); 534 + expect(Mat3.transformPoint(m, point)).toEqual(expected); 535 + }); 536 + 537 + it("should handle negative scale (flip)", () => { 538 + const m = Mat3.scale(-1, 1); 539 + const result = Mat3.transformPoint(m, { x: 5, y: 10 }); 540 + expect(result).toEqual({ x: -5, y: 10 }); 541 + }); 542 + }); 543 + 544 + describe("rotate", () => { 545 + it.each([ 546 + { 547 + description: "90 degrees counterclockwise", 548 + angle: Math.PI / 2, 549 + point: { x: 1, y: 0 }, 550 + expected: { x: 0, y: 1 }, 551 + }, 552 + { description: "180 degrees", angle: Math.PI, point: { x: 1, y: 0 }, expected: { x: -1, y: 0 } }, 553 + { description: "zero rotation", angle: 0, point: { x: 5, y: 10 }, expected: { x: 5, y: 10 } }, 554 + { 555 + description: "360 degrees (full rotation)", 556 + angle: 2 * Math.PI, 557 + point: { x: 5, y: 10 }, 558 + expected: { x: 5, y: 10 }, 559 + }, 560 + { description: "-90 degrees (clockwise)", angle: -Math.PI / 2, point: { x: 1, y: 0 }, expected: { x: 0, y: -1 } }, 561 + ])("should handle $description", ({ angle, point, expected }) => { 562 + const m = Mat3.rotate(angle); 563 + const result = Mat3.transformPoint(m, point); 564 + expect(result.x).toBeCloseTo(expected.x, 10); 565 + expect(result.y).toBeCloseTo(expected.y, 10); 566 + }); 567 + }); 568 + 569 + describe("multiply", () => { 570 + it("should combine translations", () => { 571 + const a = Mat3.translate(5, 0); 572 + const b = Mat3.translate(0, 10); 573 + const result = Mat3.multiply(a, b); 574 + const transformed = Mat3.transformPoint(result, { x: 0, y: 0 }); 575 + expect(transformed).toEqual({ x: 5, y: 10 }); 576 + }); 577 + 578 + it("should apply transforms right to left", () => { 579 + const translate = Mat3.translate(10, 0); 580 + const scale = Mat3.scale(2, 1); 581 + const result = Mat3.multiply(translate, scale); 582 + const transformed = Mat3.transformPoint(result, { x: 5, y: 0 }); 583 + expect(transformed.x).toBeCloseTo(20); 584 + }); 585 + 586 + it("should handle identity multiplication", () => { 587 + const m = Mat3.translate(5, 10); 588 + const identity = Mat3.identity(); 589 + expect(Mat3.equals(Mat3.multiply(m, identity), m)).toBe(true); 590 + expect(Mat3.equals(Mat3.multiply(identity, m), m)).toBe(true); 591 + }); 592 + 593 + it("should be associative", () => { 594 + const a = Mat3.translate(1, 2); 595 + const b = Mat3.scale(2, 3); 596 + const c = Mat3.rotate(Math.PI / 4); 597 + const result1 = Mat3.multiply(Mat3.multiply(a, b), c); 598 + const result2 = Mat3.multiply(a, Mat3.multiply(b, c)); 599 + expect(Mat3.equals(result1, result2)).toBe(true); 600 + }); 601 + 602 + it("should not be commutative", () => { 603 + const a = Mat3.translate(10, 0); 604 + const b = Mat3.scale(2, 1); 605 + const result1 = Mat3.multiply(a, b); 606 + const result2 = Mat3.multiply(b, a); 607 + expect(Mat3.equals(result1, result2)).toBe(false); 608 + }); 609 + }); 610 + 611 + describe("transformPoint", () => { 612 + it("should transform through combined transforms", () => { 613 + const m = Mat3.multiply(Mat3.translate(10, 20), Mat3.multiply(Mat3.rotate(Math.PI / 2), Mat3.scale(2, 2))); 614 + const result = Mat3.transformPoint(m, { x: 1, y: 0 }); 615 + expect(result.x).toBeCloseTo(10); 616 + expect(result.y).toBeCloseTo(22); 617 + }); 618 + 619 + it("should handle origin point", () => { 620 + const m = Mat3.translate(5, 10); 621 + expect(Mat3.transformPoint(m, { x: 0, y: 0 })).toEqual({ x: 5, y: 10 }); 622 + }); 623 + }); 624 + 625 + describe("invert", () => { 626 + it.each([{ description: "translation", matrix: Mat3.translate(5, 10) }, { 627 + description: "scale", 628 + matrix: Mat3.scale(2, 3), 629 + }, { description: "rotation", matrix: Mat3.rotate(Math.PI / 3) }])("should invert $description", ({ matrix }) => { 630 + const inv = Mat3.invert(matrix); 631 + expect(inv).not.toBeNull(); 632 + 633 + const p = { x: 10, y: 20 }; 634 + const transformed = Mat3.transformPoint(matrix, p); 635 + const restored = Mat3.transformPoint(inv!, transformed); 636 + 637 + expect(restored.x).toBeCloseTo(p.x); 638 + expect(restored.y).toBeCloseTo(p.y); 639 + }); 640 + 641 + it("should invert identity to identity", () => { 642 + const m = Mat3.identity(); 643 + const inv = Mat3.invert(m); 644 + expect(inv).not.toBeNull(); 645 + expect(Mat3.equals(inv!, m)).toBe(true); 646 + }); 647 + 648 + it("should return null for non-invertible matrix", () => { 649 + const m = Mat3.scale(0, 0); 650 + expect(Mat3.invert(m)).toBeNull(); 651 + }); 652 + 653 + it("should satisfy M * M^-1 = I", () => { 654 + const m = Mat3.multiply(Mat3.translate(5, 10), Mat3.multiply(Mat3.rotate(0.5), Mat3.scale(2, 3))); 655 + const inv = Mat3.invert(m); 656 + expect(inv).not.toBeNull(); 657 + 658 + const identity = Mat3.multiply(m, inv!); 659 + expect(Mat3.equals(identity, Mat3.identity(), 1e-10)).toBe(true); 660 + }); 661 + }); 662 + 663 + describe("determinant", () => { 664 + it.each([ 665 + { description: "identity", matrix: Mat3.identity(), expected: 1 }, 666 + { description: "translation", matrix: Mat3.translate(5, 10), expected: 1 }, 667 + { description: "scale 2x3", matrix: Mat3.scale(2, 3), expected: 6 }, 668 + { description: "rotation", matrix: Mat3.rotate(Math.PI / 4), expected: 1 }, 669 + { description: "singular matrix", matrix: Mat3.scale(0, 5), expected: 0 }, 670 + { description: "reflection", matrix: Mat3.scale(-1, 1), expected: -1 }, 671 + ])("should calculate determinant for $description", ({ matrix, expected }) => { 672 + expect(Mat3.determinant(matrix)).toBeCloseTo(expected, 10); 673 + }); 674 + }); 675 + 676 + describe("clone and equals", () => { 677 + it("should clone matrix", () => { 678 + const m = Mat3.translate(5, 10); 679 + const cloned = Mat3.clone(m); 680 + expect(cloned).toEqual(m); 681 + expect(cloned).not.toBe(m); 682 + }); 683 + 684 + it.each([ 685 + { description: "identical matrices", a: Mat3.translate(5, 10), b: Mat3.translate(5, 10), expected: true }, 686 + { description: "different matrices", a: Mat3.translate(5, 10), b: Mat3.translate(10, 5), expected: false }, 687 + ])("should compare $description", ({ a, b, expected }) => { 688 + expect(Mat3.equals(a, b)).toBe(expected); 689 + }); 690 + 691 + it("should use epsilon for floating point comparison", () => { 692 + const a = Mat3.rotate(Math.PI / 4); 693 + const b = Mat3.clone(a); 694 + b[0] += 1e-15; 695 + expect(Mat3.equals(a, b)).toBe(true); 696 + }); 697 + 698 + it("should allow custom epsilon", () => { 699 + const a = Mat3.identity(); 700 + const b = Mat3.identity(); 701 + b[0] = 1.001; 702 + expect(Mat3.equals(a, b, 0.01)).toBe(true); 703 + expect(Mat3.equals(a, b, 0.0001)).toBe(false); 704 + }); 705 + }); 706 + 707 + describe("fromTransform", () => { 708 + it("should create combined transform matrix", () => { 709 + const m = Mat3.fromTransform(10, 20, 0, 1, 1); 710 + const expected = Mat3.translate(10, 20); 711 + expect(Mat3.equals(m, expected)).toBe(true); 712 + }); 713 + 714 + it("should handle all transforms", () => { 715 + const tx = 10, ty = 20; 716 + const rotation = Math.PI / 4; 717 + const sx = 2, sy = 3; 718 + 719 + const m = Mat3.fromTransform(tx, ty, rotation, sx, sy); 720 + const p = { x: 1, y: 0 }; 721 + const result = Mat3.transformPoint(m, p); 722 + 723 + const scaled = { x: 2, y: 0 }; 724 + const rotated = { x: scaled.x * Math.cos(rotation), y: scaled.x * Math.sin(rotation) }; 725 + const translated = { x: rotated.x + tx, y: rotated.y + ty }; 726 + 727 + expect(result.x).toBeCloseTo(translated.x); 728 + expect(result.y).toBeCloseTo(translated.y); 729 + }); 730 + 731 + it("should match manual composition", () => { 732 + const tx = 5, ty = 10; 733 + const rotation = Math.PI / 6; 734 + const sx = 2, sy = 3; 735 + 736 + const m1 = Mat3.fromTransform(tx, ty, rotation, sx, sy); 737 + const m2 = Mat3.multiply(Mat3.translate(tx, ty), Mat3.multiply(Mat3.rotate(rotation), Mat3.scale(sx, sy))); 738 + 739 + expect(Mat3.equals(m1, m2, 1e-10)).toBe(true); 740 + }); 741 + }); 742 + 743 + describe("edge cases and numerical stability", () => { 744 + it.each([{ 745 + description: "very large values", 746 + matrix: Mat3.translate(1e10, 1e10), 747 + point: { x: 0, y: 0 }, 748 + expected: { x: 1e10, y: 1e10 }, 749 + }, { 750 + description: "very small values", 751 + matrix: Mat3.scale(1e-10, 1e-10), 752 + point: { x: 1e10, y: 1e10 }, 753 + expected: { x: 1, y: 1 }, 754 + }])("should handle $description", ({ matrix, point, expected }) => { 755 + const result = Mat3.transformPoint(matrix, point); 756 + expect(result.x).toBeCloseTo(expected.x); 757 + expect(result.y).toBeCloseTo(expected.y); 758 + }); 759 + 760 + it("should handle accumulated rotations", () => { 761 + let m = Mat3.identity(); 762 + for (let i = 0; i < 100; i++) { 763 + m = Mat3.multiply(m, Mat3.rotate((2 * Math.PI) / 100)); 764 + } 765 + const result = Mat3.transformPoint(m, { x: 1, y: 0 }); 766 + expect(result.x).toBeCloseTo(1, 5); 767 + expect(result.y).toBeCloseTo(0, 5); 768 + }); 769 + }); 770 + });
+1032
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 devDependencies: 11 + '@eslint/js': 12 + specifier: ^9.39.2 13 + version: 9.39.2 11 14 dprint: 12 15 specifier: ^0.50.2 13 16 version: 0.50.2 17 + eslint: 18 + specifier: ^9.39.2 19 + version: 9.39.2(jiti@2.6.1) 20 + eslint-plugin-unicorn: 21 + specifier: ^62.0.0 22 + version: 62.0.0(eslint@9.39.2(jiti@2.6.1)) 23 + globals: 24 + specifier: ^16.5.0 25 + version: 16.5.0 26 + typescript: 27 + specifier: ^5.9.3 28 + version: 5.9.3 29 + typescript-eslint: 30 + specifier: ^8.50.0 31 + version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 14 32 15 33 packages/core: 16 34 devDependencies: ··· 281 299 cpu: [x64] 282 300 os: [win32] 283 301 302 + '@eslint-community/eslint-utils@4.9.0': 303 + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} 304 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 305 + peerDependencies: 306 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 307 + 308 + '@eslint-community/regexpp@4.12.2': 309 + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} 310 + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 311 + 312 + '@eslint/config-array@0.21.1': 313 + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} 314 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 315 + 316 + '@eslint/config-helpers@0.4.2': 317 + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} 318 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 319 + 320 + '@eslint/core@0.17.0': 321 + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} 322 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 323 + 324 + '@eslint/eslintrc@3.3.3': 325 + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} 326 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 327 + 328 + '@eslint/js@9.39.2': 329 + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} 330 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 331 + 332 + '@eslint/object-schema@2.1.7': 333 + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} 334 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 335 + 336 + '@eslint/plugin-kit@0.4.1': 337 + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} 338 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 339 + 340 + '@humanfs/core@0.19.1': 341 + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 342 + engines: {node: '>=18.18.0'} 343 + 344 + '@humanfs/node@0.16.7': 345 + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} 346 + engines: {node: '>=18.18.0'} 347 + 348 + '@humanwhocodes/module-importer@1.0.1': 349 + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 350 + engines: {node: '>=12.22'} 351 + 352 + '@humanwhocodes/retry@0.4.3': 353 + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} 354 + engines: {node: '>=18.18'} 355 + 284 356 '@jridgewell/gen-mapping@0.3.13': 285 357 resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 286 358 ··· 508 580 '@types/estree@1.0.8': 509 581 resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 510 582 583 + '@types/json-schema@7.0.15': 584 + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 585 + 511 586 '@types/node@25.0.3': 512 587 resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} 513 588 589 + '@typescript-eslint/eslint-plugin@8.50.0': 590 + resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} 591 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 592 + peerDependencies: 593 + '@typescript-eslint/parser': ^8.50.0 594 + eslint: ^8.57.0 || ^9.0.0 595 + typescript: '>=4.8.4 <6.0.0' 596 + 597 + '@typescript-eslint/parser@8.50.0': 598 + resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} 599 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 600 + peerDependencies: 601 + eslint: ^8.57.0 || ^9.0.0 602 + typescript: '>=4.8.4 <6.0.0' 603 + 604 + '@typescript-eslint/project-service@8.50.0': 605 + resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} 606 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 607 + peerDependencies: 608 + typescript: '>=4.8.4 <6.0.0' 609 + 610 + '@typescript-eslint/scope-manager@8.50.0': 611 + resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} 612 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 613 + 614 + '@typescript-eslint/tsconfig-utils@8.50.0': 615 + resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} 616 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 617 + peerDependencies: 618 + typescript: '>=4.8.4 <6.0.0' 619 + 620 + '@typescript-eslint/type-utils@8.50.0': 621 + resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} 622 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 623 + peerDependencies: 624 + eslint: ^8.57.0 || ^9.0.0 625 + typescript: '>=4.8.4 <6.0.0' 626 + 627 + '@typescript-eslint/types@8.50.0': 628 + resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} 629 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 630 + 631 + '@typescript-eslint/typescript-estree@8.50.0': 632 + resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} 633 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 634 + peerDependencies: 635 + typescript: '>=4.8.4 <6.0.0' 636 + 637 + '@typescript-eslint/utils@8.50.0': 638 + resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} 639 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 640 + peerDependencies: 641 + eslint: ^8.57.0 || ^9.0.0 642 + typescript: '>=4.8.4 <6.0.0' 643 + 644 + '@typescript-eslint/visitor-keys@8.50.0': 645 + resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} 646 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 647 + 514 648 '@vitest/expect@4.0.16': 515 649 resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} 516 650 ··· 540 674 '@vitest/utils@4.0.16': 541 675 resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} 542 676 677 + acorn-jsx@5.3.2: 678 + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 679 + peerDependencies: 680 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 681 + 682 + acorn@8.15.0: 683 + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 684 + engines: {node: '>=0.4.0'} 685 + hasBin: true 686 + 687 + ajv@6.12.6: 688 + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 689 + 690 + ansi-styles@4.3.0: 691 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 692 + engines: {node: '>=8'} 693 + 543 694 ansis@4.2.0: 544 695 resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} 545 696 engines: {node: '>=14'} 697 + 698 + argparse@2.0.1: 699 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 546 700 547 701 args-tokenizer@0.3.0: 548 702 resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==} ··· 555 709 resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} 556 710 engines: {node: '>=20.19.0'} 557 711 712 + balanced-match@1.0.2: 713 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 714 + 715 + baseline-browser-mapping@2.9.11: 716 + resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} 717 + hasBin: true 718 + 558 719 birpc@4.0.0: 559 720 resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} 560 721 722 + brace-expansion@1.1.12: 723 + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} 724 + 725 + brace-expansion@2.0.2: 726 + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 727 + 728 + browserslist@4.28.1: 729 + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} 730 + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 731 + hasBin: true 732 + 733 + builtin-modules@5.0.0: 734 + resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} 735 + engines: {node: '>=18.20'} 736 + 561 737 bumpp@10.3.2: 562 738 resolution: {integrity: sha512-yUUkVx5zpTywLNX97MlrqtpanI7eMMwFwLntWR2EBVDw3/Pm3aRIzCoDEGHATLIiHK9PuJC7xWI4XNWqXItSPg==} 563 739 engines: {node: '>=18'} ··· 575 751 resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 576 752 engines: {node: '>=8'} 577 753 754 + callsites@3.1.0: 755 + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 756 + engines: {node: '>=6'} 757 + 758 + caniuse-lite@1.0.30001761: 759 + resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} 760 + 578 761 chai@6.2.1: 579 762 resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} 580 763 engines: {node: '>=18'} 581 764 765 + chalk@4.1.2: 766 + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 767 + engines: {node: '>=10'} 768 + 769 + change-case@5.4.4: 770 + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} 771 + 582 772 chokidar@5.0.0: 583 773 resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} 584 774 engines: {node: '>= 20.19.0'} 775 + 776 + ci-info@4.3.1: 777 + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} 778 + engines: {node: '>=8'} 585 779 586 780 citty@0.1.6: 587 781 resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} 588 782 783 + clean-regexp@1.0.0: 784 + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} 785 + engines: {node: '>=4'} 786 + 787 + color-convert@2.0.1: 788 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 789 + engines: {node: '>=7.0.0'} 790 + 791 + color-name@1.1.4: 792 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 793 + 794 + concat-map@0.0.1: 795 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 796 + 589 797 confbox@0.2.2: 590 798 resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} 591 799 ··· 593 801 resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} 594 802 engines: {node: ^14.18.0 || >=16.10.0} 595 803 804 + core-js-compat@3.47.0: 805 + resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} 806 + 807 + cross-spawn@7.0.6: 808 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 809 + engines: {node: '>= 8'} 810 + 811 + debug@4.4.3: 812 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} 813 + engines: {node: '>=6.0'} 814 + peerDependencies: 815 + supports-color: '*' 816 + peerDependenciesMeta: 817 + supports-color: 818 + optional: true 819 + 820 + deep-is@0.1.4: 821 + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 822 + 596 823 defu@6.1.4: 597 824 resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 598 825 ··· 616 843 oxc-resolver: 617 844 optional: true 618 845 846 + electron-to-chromium@1.5.267: 847 + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} 848 + 619 849 empathic@2.0.0: 620 850 resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} 621 851 engines: {node: '>=14'} ··· 632 862 resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 633 863 engines: {node: '>=6'} 634 864 865 + escape-string-regexp@1.0.5: 866 + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 867 + engines: {node: '>=0.8.0'} 868 + 869 + escape-string-regexp@4.0.0: 870 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 871 + engines: {node: '>=10'} 872 + 873 + eslint-plugin-unicorn@62.0.0: 874 + resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==} 875 + engines: {node: ^20.10.0 || >=21.0.0} 876 + peerDependencies: 877 + eslint: '>=9.38.0' 878 + 879 + eslint-scope@8.4.0: 880 + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} 881 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 882 + 883 + eslint-visitor-keys@3.4.3: 884 + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 885 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 886 + 887 + eslint-visitor-keys@4.2.1: 888 + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} 889 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 890 + 891 + eslint@9.39.2: 892 + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} 893 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 894 + hasBin: true 895 + peerDependencies: 896 + jiti: '*' 897 + peerDependenciesMeta: 898 + jiti: 899 + optional: true 900 + 901 + espree@10.4.0: 902 + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} 903 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 904 + 905 + esquery@1.6.0: 906 + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 907 + engines: {node: '>=0.10'} 908 + 909 + esrecurse@4.3.0: 910 + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 911 + engines: {node: '>=4.0'} 912 + 913 + estraverse@5.3.0: 914 + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 915 + engines: {node: '>=4.0'} 916 + 635 917 estree-walker@3.0.3: 636 918 resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 637 919 920 + esutils@2.0.3: 921 + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 922 + engines: {node: '>=0.10.0'} 923 + 638 924 expect-type@1.3.0: 639 925 resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} 640 926 engines: {node: '>=12.0.0'} ··· 642 928 exsolve@1.0.8: 643 929 resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} 644 930 931 + fast-deep-equal@3.1.3: 932 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 933 + 934 + fast-json-stable-stringify@2.1.0: 935 + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 936 + 937 + fast-levenshtein@2.0.6: 938 + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 939 + 645 940 fdir@6.5.0: 646 941 resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 647 942 engines: {node: '>=12.0.0'} ··· 651 946 picomatch: 652 947 optional: true 653 948 949 + file-entry-cache@8.0.0: 950 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 951 + engines: {node: '>=16.0.0'} 952 + 953 + find-up-simple@1.0.1: 954 + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} 955 + engines: {node: '>=18'} 956 + 957 + find-up@5.0.0: 958 + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 959 + engines: {node: '>=10'} 960 + 961 + flat-cache@4.0.1: 962 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 963 + engines: {node: '>=16'} 964 + 965 + flatted@3.3.3: 966 + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 967 + 654 968 fsevents@2.3.3: 655 969 resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 656 970 engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} ··· 663 977 resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} 664 978 hasBin: true 665 979 980 + glob-parent@6.0.2: 981 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 982 + engines: {node: '>=10.13.0'} 983 + 984 + globals@14.0.0: 985 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 986 + engines: {node: '>=18'} 987 + 988 + globals@16.5.0: 989 + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} 990 + engines: {node: '>=18'} 991 + 992 + has-flag@4.0.0: 993 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 994 + engines: {node: '>=8'} 995 + 666 996 hookable@5.5.3: 667 997 resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} 668 998 999 + ignore@5.3.2: 1000 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 1001 + engines: {node: '>= 4'} 1002 + 1003 + ignore@7.0.5: 1004 + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} 1005 + engines: {node: '>= 4'} 1006 + 1007 + import-fresh@3.3.1: 1008 + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 1009 + engines: {node: '>=6'} 1010 + 669 1011 import-without-cache@0.2.4: 670 1012 resolution: {integrity: sha512-b/Ke0y4n26ffQhkLvgBxV/NVO/QEE6AZlrMj8DYuxBWNAAu4iMQWZTFWzKcCTEmv7VQ0ae0j8KwrlGzSy8sYQQ==} 671 1013 engines: {node: '>=20.19.0'} 672 1014 1015 + imurmurhash@0.1.4: 1016 + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 1017 + engines: {node: '>=0.8.19'} 1018 + 1019 + indent-string@5.0.0: 1020 + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} 1021 + engines: {node: '>=12'} 1022 + 1023 + is-builtin-module@5.0.0: 1024 + resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} 1025 + engines: {node: '>=18.20'} 1026 + 1027 + is-extglob@2.1.1: 1028 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1029 + engines: {node: '>=0.10.0'} 1030 + 1031 + is-glob@4.0.3: 1032 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1033 + engines: {node: '>=0.10.0'} 1034 + 1035 + isexe@2.0.0: 1036 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1037 + 673 1038 jiti@2.6.1: 674 1039 resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} 675 1040 hasBin: true 676 1041 1042 + js-yaml@4.1.1: 1043 + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 1044 + hasBin: true 1045 + 677 1046 jsesc@3.1.0: 678 1047 resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 679 1048 engines: {node: '>=6'} 680 1049 hasBin: true 681 1050 1051 + json-buffer@3.0.1: 1052 + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 1053 + 1054 + json-schema-traverse@0.4.1: 1055 + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1056 + 1057 + json-stable-stringify-without-jsonify@1.0.1: 1058 + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 1059 + 682 1060 jsonc-parser@3.3.1: 683 1061 resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} 684 1062 1063 + keyv@4.5.4: 1064 + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 1065 + 1066 + levn@0.4.1: 1067 + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1068 + engines: {node: '>= 0.8.0'} 1069 + 1070 + locate-path@6.0.0: 1071 + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1072 + engines: {node: '>=10'} 1073 + 1074 + lodash.merge@4.6.2: 1075 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1076 + 685 1077 magic-string@0.30.21: 686 1078 resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 687 1079 1080 + minimatch@3.1.2: 1081 + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1082 + 1083 + minimatch@9.0.5: 1084 + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1085 + engines: {node: '>=16 || 14 >=14.17'} 1086 + 1087 + ms@2.1.3: 1088 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1089 + 688 1090 nanoid@3.3.11: 689 1091 resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 690 1092 engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 691 1093 hasBin: true 692 1094 1095 + natural-compare@1.4.0: 1096 + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1097 + 693 1098 node-fetch-native@1.6.7: 694 1099 resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} 1100 + 1101 + node-releases@2.0.27: 1102 + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} 695 1103 696 1104 nypm@0.6.2: 697 1105 resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} ··· 703 1111 704 1112 ohash@2.0.11: 705 1113 resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} 1114 + 1115 + optionator@0.9.4: 1116 + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 1117 + engines: {node: '>= 0.8.0'} 1118 + 1119 + p-limit@3.1.0: 1120 + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1121 + engines: {node: '>=10'} 1122 + 1123 + p-locate@5.0.0: 1124 + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1125 + engines: {node: '>=10'} 706 1126 707 1127 package-manager-detector@1.6.0: 708 1128 resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} 709 1129 1130 + parent-module@1.0.1: 1131 + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1132 + engines: {node: '>=6'} 1133 + 1134 + path-exists@4.0.0: 1135 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1136 + engines: {node: '>=8'} 1137 + 1138 + path-key@3.1.1: 1139 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1140 + engines: {node: '>=8'} 1141 + 710 1142 pathe@2.0.3: 711 1143 resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 712 1144 ··· 722 1154 723 1155 pkg-types@2.3.0: 724 1156 resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} 1157 + 1158 + pluralize@8.0.0: 1159 + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} 1160 + engines: {node: '>=4'} 725 1161 726 1162 postcss@8.5.6: 727 1163 resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 728 1164 engines: {node: ^10 || ^12 || >=14} 729 1165 1166 + prelude-ls@1.2.1: 1167 + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1168 + engines: {node: '>= 0.8.0'} 1169 + 1170 + punycode@2.3.1: 1171 + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1172 + engines: {node: '>=6'} 1173 + 730 1174 quansync@1.0.0: 731 1175 resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} 732 1176 ··· 737 1181 resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} 738 1182 engines: {node: '>= 20.19.0'} 739 1183 1184 + regexp-tree@0.1.27: 1185 + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} 1186 + hasBin: true 1187 + 1188 + regjsparser@0.13.0: 1189 + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} 1190 + hasBin: true 1191 + 1192 + resolve-from@4.0.0: 1193 + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1194 + engines: {node: '>=4'} 1195 + 740 1196 resolve-pkg-maps@1.0.0: 741 1197 resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 742 1198 ··· 774 1230 engines: {node: '>=10'} 775 1231 hasBin: true 776 1232 1233 + shebang-command@2.0.0: 1234 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1235 + engines: {node: '>=8'} 1236 + 1237 + shebang-regex@3.0.0: 1238 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1239 + engines: {node: '>=8'} 1240 + 777 1241 siginfo@2.0.0: 778 1242 resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 779 1243 ··· 787 1251 std-env@3.10.0: 788 1252 resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} 789 1253 1254 + strip-indent@4.1.1: 1255 + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} 1256 + engines: {node: '>=12'} 1257 + 1258 + strip-json-comments@3.1.1: 1259 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1260 + engines: {node: '>=8'} 1261 + 1262 + supports-color@7.2.0: 1263 + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1264 + engines: {node: '>=8'} 1265 + 790 1266 tinybench@2.9.0: 791 1267 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 792 1268 ··· 806 1282 resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 807 1283 hasBin: true 808 1284 1285 + ts-api-utils@2.1.0: 1286 + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 1287 + engines: {node: '>=18.12'} 1288 + peerDependencies: 1289 + typescript: '>=4.8.4' 1290 + 809 1291 tsdown@0.18.1: 810 1292 resolution: {integrity: sha512-na4MdVA8QS9Zw++0KovGpjvw1BY5WvoCWcE4Aw0dyfff9nWK8BPzniQEVs+apGUg3DHaYMDfs+XiFaDDgqDDzQ==} 811 1293 engines: {node: '>=20.19.0'} ··· 834 1316 tslib@2.8.1: 835 1317 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 836 1318 1319 + type-check@0.4.0: 1320 + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1321 + engines: {node: '>= 0.8.0'} 1322 + 1323 + typescript-eslint@8.50.0: 1324 + resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} 1325 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1326 + peerDependencies: 1327 + eslint: ^8.57.0 || ^9.0.0 1328 + typescript: '>=4.8.4 <6.0.0' 1329 + 837 1330 typescript@5.9.3: 838 1331 resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 839 1332 engines: {node: '>=14.17'} ··· 854 1347 peerDependenciesMeta: 855 1348 synckit: 856 1349 optional: true 1350 + 1351 + update-browserslist-db@1.2.3: 1352 + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} 1353 + hasBin: true 1354 + peerDependencies: 1355 + browserslist: '>= 4.21.0' 1356 + 1357 + uri-js@4.4.1: 1358 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 857 1359 858 1360 vite@7.3.0: 859 1361 resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} ··· 929 1431 jsdom: 930 1432 optional: true 931 1433 1434 + which@2.0.2: 1435 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1436 + engines: {node: '>= 8'} 1437 + hasBin: true 1438 + 932 1439 why-is-node-running@2.3.0: 933 1440 resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 934 1441 engines: {node: '>=8'} 935 1442 hasBin: true 936 1443 1444 + word-wrap@1.2.5: 1445 + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 1446 + engines: {node: '>=0.10.0'} 1447 + 937 1448 yaml@2.8.2: 938 1449 resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} 939 1450 engines: {node: '>= 14.6'} 940 1451 hasBin: true 1452 + 1453 + yocto-queue@0.1.0: 1454 + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1455 + engines: {node: '>=10'} 941 1456 942 1457 snapshots: 943 1458 ··· 1083 1598 '@esbuild/win32-x64@0.27.2': 1084 1599 optional: true 1085 1600 1601 + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': 1602 + dependencies: 1603 + eslint: 9.39.2(jiti@2.6.1) 1604 + eslint-visitor-keys: 3.4.3 1605 + 1606 + '@eslint-community/regexpp@4.12.2': {} 1607 + 1608 + '@eslint/config-array@0.21.1': 1609 + dependencies: 1610 + '@eslint/object-schema': 2.1.7 1611 + debug: 4.4.3 1612 + minimatch: 3.1.2 1613 + transitivePeerDependencies: 1614 + - supports-color 1615 + 1616 + '@eslint/config-helpers@0.4.2': 1617 + dependencies: 1618 + '@eslint/core': 0.17.0 1619 + 1620 + '@eslint/core@0.17.0': 1621 + dependencies: 1622 + '@types/json-schema': 7.0.15 1623 + 1624 + '@eslint/eslintrc@3.3.3': 1625 + dependencies: 1626 + ajv: 6.12.6 1627 + debug: 4.4.3 1628 + espree: 10.4.0 1629 + globals: 14.0.0 1630 + ignore: 5.3.2 1631 + import-fresh: 3.3.1 1632 + js-yaml: 4.1.1 1633 + minimatch: 3.1.2 1634 + strip-json-comments: 3.1.1 1635 + transitivePeerDependencies: 1636 + - supports-color 1637 + 1638 + '@eslint/js@9.39.2': {} 1639 + 1640 + '@eslint/object-schema@2.1.7': {} 1641 + 1642 + '@eslint/plugin-kit@0.4.1': 1643 + dependencies: 1644 + '@eslint/core': 0.17.0 1645 + levn: 0.4.1 1646 + 1647 + '@humanfs/core@0.19.1': {} 1648 + 1649 + '@humanfs/node@0.16.7': 1650 + dependencies: 1651 + '@humanfs/core': 0.19.1 1652 + '@humanwhocodes/retry': 0.4.3 1653 + 1654 + '@humanwhocodes/module-importer@1.0.1': {} 1655 + 1656 + '@humanwhocodes/retry@0.4.3': {} 1657 + 1086 1658 '@jridgewell/gen-mapping@0.3.13': 1087 1659 dependencies: 1088 1660 '@jridgewell/sourcemap-codec': 1.5.5 ··· 1235 1807 1236 1808 '@types/estree@1.0.8': {} 1237 1809 1810 + '@types/json-schema@7.0.15': {} 1811 + 1238 1812 '@types/node@25.0.3': 1239 1813 dependencies: 1240 1814 undici-types: 7.16.0 1241 1815 1816 + '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 1817 + dependencies: 1818 + '@eslint-community/regexpp': 4.12.2 1819 + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 1820 + '@typescript-eslint/scope-manager': 8.50.0 1821 + '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 1822 + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 1823 + '@typescript-eslint/visitor-keys': 8.50.0 1824 + eslint: 9.39.2(jiti@2.6.1) 1825 + ignore: 7.0.5 1826 + natural-compare: 1.4.0 1827 + ts-api-utils: 2.1.0(typescript@5.9.3) 1828 + typescript: 5.9.3 1829 + transitivePeerDependencies: 1830 + - supports-color 1831 + 1832 + '@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 1833 + dependencies: 1834 + '@typescript-eslint/scope-manager': 8.50.0 1835 + '@typescript-eslint/types': 8.50.0 1836 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) 1837 + '@typescript-eslint/visitor-keys': 8.50.0 1838 + debug: 4.4.3 1839 + eslint: 9.39.2(jiti@2.6.1) 1840 + typescript: 5.9.3 1841 + transitivePeerDependencies: 1842 + - supports-color 1843 + 1844 + '@typescript-eslint/project-service@8.50.0(typescript@5.9.3)': 1845 + dependencies: 1846 + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) 1847 + '@typescript-eslint/types': 8.50.0 1848 + debug: 4.4.3 1849 + typescript: 5.9.3 1850 + transitivePeerDependencies: 1851 + - supports-color 1852 + 1853 + '@typescript-eslint/scope-manager@8.50.0': 1854 + dependencies: 1855 + '@typescript-eslint/types': 8.50.0 1856 + '@typescript-eslint/visitor-keys': 8.50.0 1857 + 1858 + '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': 1859 + dependencies: 1860 + typescript: 5.9.3 1861 + 1862 + '@typescript-eslint/type-utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 1863 + dependencies: 1864 + '@typescript-eslint/types': 8.50.0 1865 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) 1866 + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 1867 + debug: 4.4.3 1868 + eslint: 9.39.2(jiti@2.6.1) 1869 + ts-api-utils: 2.1.0(typescript@5.9.3) 1870 + typescript: 5.9.3 1871 + transitivePeerDependencies: 1872 + - supports-color 1873 + 1874 + '@typescript-eslint/types@8.50.0': {} 1875 + 1876 + '@typescript-eslint/typescript-estree@8.50.0(typescript@5.9.3)': 1877 + dependencies: 1878 + '@typescript-eslint/project-service': 8.50.0(typescript@5.9.3) 1879 + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) 1880 + '@typescript-eslint/types': 8.50.0 1881 + '@typescript-eslint/visitor-keys': 8.50.0 1882 + debug: 4.4.3 1883 + minimatch: 9.0.5 1884 + semver: 7.7.3 1885 + tinyglobby: 0.2.15 1886 + ts-api-utils: 2.1.0(typescript@5.9.3) 1887 + typescript: 5.9.3 1888 + transitivePeerDependencies: 1889 + - supports-color 1890 + 1891 + '@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 1892 + dependencies: 1893 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) 1894 + '@typescript-eslint/scope-manager': 8.50.0 1895 + '@typescript-eslint/types': 8.50.0 1896 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) 1897 + eslint: 9.39.2(jiti@2.6.1) 1898 + typescript: 5.9.3 1899 + transitivePeerDependencies: 1900 + - supports-color 1901 + 1902 + '@typescript-eslint/visitor-keys@8.50.0': 1903 + dependencies: 1904 + '@typescript-eslint/types': 8.50.0 1905 + eslint-visitor-keys: 4.2.1 1906 + 1242 1907 '@vitest/expect@4.0.16': 1243 1908 dependencies: 1244 1909 '@standard-schema/spec': 1.1.0 ··· 1278 1943 '@vitest/pretty-format': 4.0.16 1279 1944 tinyrainbow: 3.0.3 1280 1945 1946 + acorn-jsx@5.3.2(acorn@8.15.0): 1947 + dependencies: 1948 + acorn: 8.15.0 1949 + 1950 + acorn@8.15.0: {} 1951 + 1952 + ajv@6.12.6: 1953 + dependencies: 1954 + fast-deep-equal: 3.1.3 1955 + fast-json-stable-stringify: 2.1.0 1956 + json-schema-traverse: 0.4.1 1957 + uri-js: 4.4.1 1958 + 1959 + ansi-styles@4.3.0: 1960 + dependencies: 1961 + color-convert: 2.0.1 1962 + 1281 1963 ansis@4.2.0: {} 1964 + 1965 + argparse@2.0.1: {} 1282 1966 1283 1967 args-tokenizer@0.3.0: {} 1284 1968 ··· 1289 1973 '@babel/parser': 7.28.5 1290 1974 pathe: 2.0.3 1291 1975 1976 + balanced-match@1.0.2: {} 1977 + 1978 + baseline-browser-mapping@2.9.11: {} 1979 + 1292 1980 birpc@4.0.0: {} 1293 1981 1982 + brace-expansion@1.1.12: 1983 + dependencies: 1984 + balanced-match: 1.0.2 1985 + concat-map: 0.0.1 1986 + 1987 + brace-expansion@2.0.2: 1988 + dependencies: 1989 + balanced-match: 1.0.2 1990 + 1991 + browserslist@4.28.1: 1992 + dependencies: 1993 + baseline-browser-mapping: 2.9.11 1994 + caniuse-lite: 1.0.30001761 1995 + electron-to-chromium: 1.5.267 1996 + node-releases: 2.0.27 1997 + update-browserslist-db: 1.2.3(browserslist@4.28.1) 1998 + 1999 + builtin-modules@5.0.0: {} 2000 + 1294 2001 bumpp@10.3.2: 1295 2002 dependencies: 1296 2003 ansis: 4.2.0 ··· 1324 2031 1325 2032 cac@6.7.14: {} 1326 2033 2034 + callsites@3.1.0: {} 2035 + 2036 + caniuse-lite@1.0.30001761: {} 2037 + 1327 2038 chai@6.2.1: {} 2039 + 2040 + chalk@4.1.2: 2041 + dependencies: 2042 + ansi-styles: 4.3.0 2043 + supports-color: 7.2.0 2044 + 2045 + change-case@5.4.4: {} 1328 2046 1329 2047 chokidar@5.0.0: 1330 2048 dependencies: 1331 2049 readdirp: 5.0.0 1332 2050 2051 + ci-info@4.3.1: {} 2052 + 1333 2053 citty@0.1.6: 1334 2054 dependencies: 1335 2055 consola: 3.4.2 1336 2056 2057 + clean-regexp@1.0.0: 2058 + dependencies: 2059 + escape-string-regexp: 1.0.5 2060 + 2061 + color-convert@2.0.1: 2062 + dependencies: 2063 + color-name: 1.1.4 2064 + 2065 + color-name@1.1.4: {} 2066 + 2067 + concat-map@0.0.1: {} 2068 + 1337 2069 confbox@0.2.2: {} 1338 2070 1339 2071 consola@3.4.2: {} 1340 2072 2073 + core-js-compat@3.47.0: 2074 + dependencies: 2075 + browserslist: 4.28.1 2076 + 2077 + cross-spawn@7.0.6: 2078 + dependencies: 2079 + path-key: 3.1.1 2080 + shebang-command: 2.0.0 2081 + which: 2.0.2 2082 + 2083 + debug@4.4.3: 2084 + dependencies: 2085 + ms: 2.1.3 2086 + 2087 + deep-is@0.1.4: {} 2088 + 1341 2089 defu@6.1.4: {} 1342 2090 1343 2091 destr@2.0.5: {} ··· 1357 2105 '@dprint/win32-x64': 0.50.2 1358 2106 1359 2107 dts-resolver@2.1.3: {} 2108 + 2109 + electron-to-chromium@1.5.267: {} 1360 2110 1361 2111 empathic@2.0.0: {} 1362 2112 ··· 1393 2143 1394 2144 escalade@3.2.0: {} 1395 2145 2146 + escape-string-regexp@1.0.5: {} 2147 + 2148 + escape-string-regexp@4.0.0: {} 2149 + 2150 + eslint-plugin-unicorn@62.0.0(eslint@9.39.2(jiti@2.6.1)): 2151 + dependencies: 2152 + '@babel/helper-validator-identifier': 7.28.5 2153 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) 2154 + '@eslint/plugin-kit': 0.4.1 2155 + change-case: 5.4.4 2156 + ci-info: 4.3.1 2157 + clean-regexp: 1.0.0 2158 + core-js-compat: 3.47.0 2159 + eslint: 9.39.2(jiti@2.6.1) 2160 + esquery: 1.6.0 2161 + find-up-simple: 1.0.1 2162 + globals: 16.5.0 2163 + indent-string: 5.0.0 2164 + is-builtin-module: 5.0.0 2165 + jsesc: 3.1.0 2166 + pluralize: 8.0.0 2167 + regexp-tree: 0.1.27 2168 + regjsparser: 0.13.0 2169 + semver: 7.7.3 2170 + strip-indent: 4.1.1 2171 + 2172 + eslint-scope@8.4.0: 2173 + dependencies: 2174 + esrecurse: 4.3.0 2175 + estraverse: 5.3.0 2176 + 2177 + eslint-visitor-keys@3.4.3: {} 2178 + 2179 + eslint-visitor-keys@4.2.1: {} 2180 + 2181 + eslint@9.39.2(jiti@2.6.1): 2182 + dependencies: 2183 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) 2184 + '@eslint-community/regexpp': 4.12.2 2185 + '@eslint/config-array': 0.21.1 2186 + '@eslint/config-helpers': 0.4.2 2187 + '@eslint/core': 0.17.0 2188 + '@eslint/eslintrc': 3.3.3 2189 + '@eslint/js': 9.39.2 2190 + '@eslint/plugin-kit': 0.4.1 2191 + '@humanfs/node': 0.16.7 2192 + '@humanwhocodes/module-importer': 1.0.1 2193 + '@humanwhocodes/retry': 0.4.3 2194 + '@types/estree': 1.0.8 2195 + ajv: 6.12.6 2196 + chalk: 4.1.2 2197 + cross-spawn: 7.0.6 2198 + debug: 4.4.3 2199 + escape-string-regexp: 4.0.0 2200 + eslint-scope: 8.4.0 2201 + eslint-visitor-keys: 4.2.1 2202 + espree: 10.4.0 2203 + esquery: 1.6.0 2204 + esutils: 2.0.3 2205 + fast-deep-equal: 3.1.3 2206 + file-entry-cache: 8.0.0 2207 + find-up: 5.0.0 2208 + glob-parent: 6.0.2 2209 + ignore: 5.3.2 2210 + imurmurhash: 0.1.4 2211 + is-glob: 4.0.3 2212 + json-stable-stringify-without-jsonify: 1.0.1 2213 + lodash.merge: 4.6.2 2214 + minimatch: 3.1.2 2215 + natural-compare: 1.4.0 2216 + optionator: 0.9.4 2217 + optionalDependencies: 2218 + jiti: 2.6.1 2219 + transitivePeerDependencies: 2220 + - supports-color 2221 + 2222 + espree@10.4.0: 2223 + dependencies: 2224 + acorn: 8.15.0 2225 + acorn-jsx: 5.3.2(acorn@8.15.0) 2226 + eslint-visitor-keys: 4.2.1 2227 + 2228 + esquery@1.6.0: 2229 + dependencies: 2230 + estraverse: 5.3.0 2231 + 2232 + esrecurse@4.3.0: 2233 + dependencies: 2234 + estraverse: 5.3.0 2235 + 2236 + estraverse@5.3.0: {} 2237 + 1396 2238 estree-walker@3.0.3: 1397 2239 dependencies: 1398 2240 '@types/estree': 1.0.8 1399 2241 2242 + esutils@2.0.3: {} 2243 + 1400 2244 expect-type@1.3.0: {} 1401 2245 1402 2246 exsolve@1.0.8: {} 1403 2247 2248 + fast-deep-equal@3.1.3: {} 2249 + 2250 + fast-json-stable-stringify@2.1.0: {} 2251 + 2252 + fast-levenshtein@2.0.6: {} 2253 + 1404 2254 fdir@6.5.0(picomatch@4.0.3): 1405 2255 optionalDependencies: 1406 2256 picomatch: 4.0.3 1407 2257 2258 + file-entry-cache@8.0.0: 2259 + dependencies: 2260 + flat-cache: 4.0.1 2261 + 2262 + find-up-simple@1.0.1: {} 2263 + 2264 + find-up@5.0.0: 2265 + dependencies: 2266 + locate-path: 6.0.0 2267 + path-exists: 4.0.0 2268 + 2269 + flat-cache@4.0.1: 2270 + dependencies: 2271 + flatted: 3.3.3 2272 + keyv: 4.5.4 2273 + 2274 + flatted@3.3.3: {} 2275 + 1408 2276 fsevents@2.3.3: 1409 2277 optional: true 1410 2278 ··· 1421 2289 nypm: 0.6.2 1422 2290 pathe: 2.0.3 1423 2291 2292 + glob-parent@6.0.2: 2293 + dependencies: 2294 + is-glob: 4.0.3 2295 + 2296 + globals@14.0.0: {} 2297 + 2298 + globals@16.5.0: {} 2299 + 2300 + has-flag@4.0.0: {} 2301 + 1424 2302 hookable@5.5.3: {} 2303 + 2304 + ignore@5.3.2: {} 2305 + 2306 + ignore@7.0.5: {} 2307 + 2308 + import-fresh@3.3.1: 2309 + dependencies: 2310 + parent-module: 1.0.1 2311 + resolve-from: 4.0.0 1425 2312 1426 2313 import-without-cache@0.2.4: {} 2314 + 2315 + imurmurhash@0.1.4: {} 2316 + 2317 + indent-string@5.0.0: {} 2318 + 2319 + is-builtin-module@5.0.0: 2320 + dependencies: 2321 + builtin-modules: 5.0.0 2322 + 2323 + is-extglob@2.1.1: {} 2324 + 2325 + is-glob@4.0.3: 2326 + dependencies: 2327 + is-extglob: 2.1.1 2328 + 2329 + isexe@2.0.0: {} 1427 2330 1428 2331 jiti@2.6.1: {} 1429 2332 2333 + js-yaml@4.1.1: 2334 + dependencies: 2335 + argparse: 2.0.1 2336 + 1430 2337 jsesc@3.1.0: {} 1431 2338 2339 + json-buffer@3.0.1: {} 2340 + 2341 + json-schema-traverse@0.4.1: {} 2342 + 2343 + json-stable-stringify-without-jsonify@1.0.1: {} 2344 + 1432 2345 jsonc-parser@3.3.1: {} 1433 2346 2347 + keyv@4.5.4: 2348 + dependencies: 2349 + json-buffer: 3.0.1 2350 + 2351 + levn@0.4.1: 2352 + dependencies: 2353 + prelude-ls: 1.2.1 2354 + type-check: 0.4.0 2355 + 2356 + locate-path@6.0.0: 2357 + dependencies: 2358 + p-locate: 5.0.0 2359 + 2360 + lodash.merge@4.6.2: {} 2361 + 1434 2362 magic-string@0.30.21: 1435 2363 dependencies: 1436 2364 '@jridgewell/sourcemap-codec': 1.5.5 1437 2365 2366 + minimatch@3.1.2: 2367 + dependencies: 2368 + brace-expansion: 1.1.12 2369 + 2370 + minimatch@9.0.5: 2371 + dependencies: 2372 + brace-expansion: 2.0.2 2373 + 2374 + ms@2.1.3: {} 2375 + 1438 2376 nanoid@3.3.11: {} 1439 2377 2378 + natural-compare@1.4.0: {} 2379 + 1440 2380 node-fetch-native@1.6.7: {} 2381 + 2382 + node-releases@2.0.27: {} 1441 2383 1442 2384 nypm@0.6.2: 1443 2385 dependencies: ··· 1451 2393 1452 2394 ohash@2.0.11: {} 1453 2395 2396 + optionator@0.9.4: 2397 + dependencies: 2398 + deep-is: 0.1.4 2399 + fast-levenshtein: 2.0.6 2400 + levn: 0.4.1 2401 + prelude-ls: 1.2.1 2402 + type-check: 0.4.0 2403 + word-wrap: 1.2.5 2404 + 2405 + p-limit@3.1.0: 2406 + dependencies: 2407 + yocto-queue: 0.1.0 2408 + 2409 + p-locate@5.0.0: 2410 + dependencies: 2411 + p-limit: 3.1.0 2412 + 1454 2413 package-manager-detector@1.6.0: {} 1455 2414 2415 + parent-module@1.0.1: 2416 + dependencies: 2417 + callsites: 3.1.0 2418 + 2419 + path-exists@4.0.0: {} 2420 + 2421 + path-key@3.1.1: {} 2422 + 1456 2423 pathe@2.0.3: {} 1457 2424 1458 2425 perfect-debounce@2.0.0: {} ··· 1467 2434 exsolve: 1.0.8 1468 2435 pathe: 2.0.3 1469 2436 2437 + pluralize@8.0.0: {} 2438 + 1470 2439 postcss@8.5.6: 1471 2440 dependencies: 1472 2441 nanoid: 3.3.11 1473 2442 picocolors: 1.1.1 1474 2443 source-map-js: 1.2.1 1475 2444 2445 + prelude-ls@1.2.1: {} 2446 + 2447 + punycode@2.3.1: {} 2448 + 1476 2449 quansync@1.0.0: {} 1477 2450 1478 2451 rc9@2.1.2: ··· 1481 2454 destr: 2.0.5 1482 2455 1483 2456 readdirp@5.0.0: {} 2457 + 2458 + regexp-tree@0.1.27: {} 2459 + 2460 + regjsparser@0.13.0: 2461 + dependencies: 2462 + jsesc: 3.1.0 2463 + 2464 + resolve-from@4.0.0: {} 1484 2465 1485 2466 resolve-pkg-maps@1.0.0: {} 1486 2467 ··· 1549 2530 1550 2531 semver@7.7.3: {} 1551 2532 2533 + shebang-command@2.0.0: 2534 + dependencies: 2535 + shebang-regex: 3.0.0 2536 + 2537 + shebang-regex@3.0.0: {} 2538 + 1552 2539 siginfo@2.0.0: {} 1553 2540 1554 2541 source-map-js@1.2.1: {} ··· 1557 2544 1558 2545 std-env@3.10.0: {} 1559 2546 2547 + strip-indent@4.1.1: {} 2548 + 2549 + strip-json-comments@3.1.1: {} 2550 + 2551 + supports-color@7.2.0: 2552 + dependencies: 2553 + has-flag: 4.0.0 2554 + 1560 2555 tinybench@2.9.0: {} 1561 2556 1562 2557 tinyexec@1.0.2: {} ··· 1569 2564 tinyrainbow@3.0.3: {} 1570 2565 1571 2566 tree-kill@1.2.2: {} 2567 + 2568 + ts-api-utils@2.1.0(typescript@5.9.3): 2569 + dependencies: 2570 + typescript: 5.9.3 1572 2571 1573 2572 tsdown@0.18.1(typescript@5.9.3): 1574 2573 dependencies: ··· 1600 2599 tslib@2.8.1: 1601 2600 optional: true 1602 2601 2602 + type-check@0.4.0: 2603 + dependencies: 2604 + prelude-ls: 1.2.1 2605 + 2606 + typescript-eslint@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): 2607 + dependencies: 2608 + '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 2609 + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 2610 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) 2611 + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 2612 + eslint: 9.39.2(jiti@2.6.1) 2613 + typescript: 5.9.3 2614 + transitivePeerDependencies: 2615 + - supports-color 2616 + 1603 2617 typescript@5.9.3: {} 1604 2618 1605 2619 unconfig-core@7.4.2: ··· 1612 2626 unrun@0.2.20: 1613 2627 dependencies: 1614 2628 rolldown: 1.0.0-beta.55 2629 + 2630 + update-browserslist-db@1.2.3(browserslist@4.28.1): 2631 + dependencies: 2632 + browserslist: 4.28.1 2633 + escalade: 3.2.0 2634 + picocolors: 1.1.1 2635 + 2636 + uri-js@4.4.1: 2637 + dependencies: 2638 + punycode: 2.3.1 1615 2639 1616 2640 vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2): 1617 2641 dependencies: ··· 1664 2688 - tsx 1665 2689 - yaml 1666 2690 2691 + which@2.0.2: 2692 + dependencies: 2693 + isexe: 2.0.0 2694 + 1667 2695 why-is-node-running@2.3.0: 1668 2696 dependencies: 1669 2697 siginfo: 2.0.0 1670 2698 stackback: 0.0.2 1671 2699 2700 + word-wrap@1.2.5: {} 2701 + 1672 2702 yaml@2.8.2: {} 2703 + 2704 + yocto-queue@0.1.0: {}
+1
pnpm-workspace.yaml
··· 2 2 - packages/* 3 3 4 4 onlyBuiltDependencies: 5 + - dprint 5 6 - esbuild