diff --git a/.claude/skills/add-helper/SKILL.md b/.claude/skills/add-helper/SKILL.md index 255e1ee3..329f0003 100644 --- a/.claude/skills/add-helper/SKILL.md +++ b/.claude/skills/add-helper/SKILL.md @@ -26,9 +26,9 @@ name/behavior first if it wasn't already specified. caution. `array`/`object` having *intentionally duplicated* `compact`/`equalsShallow` is the one deliberate exception — see `AGENTS.md`. -## 2. Create the four files +## 2. Create the files -Follow `CONTRIBUTING.md`'s "Creating a new helper" section (Steps 1–3) for the exact file +Follow `CONTRIBUTING.md`'s "Creating a new helper" section (Steps 1–3, 5) for the exact file templates: - `helpers//.ts` — implementation + LGPL-3.0-or-later license header + JSDoc @@ -41,6 +41,10 @@ templates: - `helpers//.example.ts` — a `HelperExamples` default export (see `scripts/examples/types.ts`) with ≥2 examples, each with a `title`, `description`, `code` string, and a throwing `assert()` +- `helpers//.bench.ts` — **optional**; match the category's own convention rather + than guessing: `ls helpers//*.bench.ts | wc -l` vs. the function count tells you + whether that category typically has one or typically doesn't. `vitest bench` supports async + callbacks, so a helper that `await`s real work isn't a reason to skip it. Do **not** hand-edit `helpers//index.ts` — it's generated at build time and gitignored; the build discovers new files automatically. @@ -56,7 +60,9 @@ pnpm typecheck && pnpm lint && pnpm test:coverage && pnpm build && pnpm coherenc ``` All must pass. Check `pnpm test:coverage`'s per-file output for the new files specifically — -100% on the aggregate can hide a gap in exactly the file just added. +100% on the aggregate can hide a gap in exactly the file just added. If a `.bench.ts` was added, +also run `npx vitest bench helpers//.bench.ts --run` to confirm it executes +cleanly (`pnpm bench` on its own runs the *entire* suite, slow and unnecessary for one file). ## 4. Commit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4493645a..20686919 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -281,9 +281,13 @@ jobs: - name: Package build metadata for website if: ${{ !inputs.dry-run }} run: | - # Package all meta/ directories for the website to consume + # Package all meta/ directories plus each category's llms.txt for the website to + # consume — llms.txt lives at build//llms.txt, outside meta/, so it needs + # its own match; without it, generate-typescript-docs.js silently skips regenerating + # llms-full.txt (warns "not found", never fails the workflow) and the site keeps + # serving whatever version it last had, indefinitely. cd build - find . -path '*/meta/*.json' | tar -czf /tmp/build-meta.tar.gz -T - + find . \( -path '*/meta/*.json' -o -name 'llms.txt' \) | tar -czf /tmp/build-meta.tar.gz -T - echo "📦 Packaged build metadata ($(du -h /tmp/build-meta.tar.gz | cut -f1))" - name: Create verified tag and GitHub Release diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index f22ee621..0fafbe6c 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc with: results_file: results.sarif results_format: sarif @@ -39,6 +39,6 @@ jobs: retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a + uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 with: sarif_file: results.sarif diff --git a/.template/bundle/package.json b/.template/bundle/package.json index 461b57cc..9984100f 100644 --- a/.template/bundle/package.json +++ b/.template/bundle/package.json @@ -2,7 +2,7 @@ "name": "@helpers4/all", "version": "{{version}}", "description": "Complete collection of helpers4 utilities - all categories in one convenient package.", - "author": "baxyz ", + "author": "baxyz ", "license": "LGPL-3.0", "homepage": "https://helpers4.dev/typescript/", "repository": { diff --git a/.template/category/package.json b/.template/category/package.json index 98c2fdf2..a499ac9d 100644 --- a/.template/category/package.json +++ b/.template/category/package.json @@ -2,7 +2,7 @@ "name": "@helpers4/{{category}}", "version": "{{version}}", "description": "A set of helpers in TS/JS, compatible with tree-shaking, for {{category}}.", - "author": "baxyz ", + "author": "baxyz ", "license": "LGPL-3.0", "homepage": "https://helpers4.dev/typescript/categories/{{category}}/", "repository": { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c6b8fb0..4c81d44d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,7 @@ helpers/ config.json # Category metadata ``` -Categories: `array`, `ci`, `color`, `commit`, `date`, `function`, `guard`, `id`, `markdown`, `node`, `number`, `object`, `observable`, `promise`, `string`, `type`, `url`, `version`. +Categories: `array`, `ci`, `color`, `commit`, `date`, `function`, `guard`, `id`, `map`, `markdown`, `node`, `number`, `object`, `observable`, `promise`, `set`, `string`, `type`, `url`, `version`. ## Creating a new helper diff --git a/README.md b/README.md index 95b8d432..2b493e7f 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Install only what you need, or grab everything at once: | Package | Description | Install | |---------|-------------|---------| | [`@helpers4/all`](https://www.npmjs.com/package/@helpers4/all) | Complete collection — all categories in one package | `npm i @helpers4/all` | -| [`@helpers4/array`](https://www.npmjs.com/package/@helpers4/array) | Array operations, chunking, comparison, and manipulation | `npm i @helpers4/array` | +| [`@helpers4/array`](https://www.npmjs.com/package/@helpers4/array) | Array operations, chunking, comparison, manipulation, and async iteration | `npm i @helpers4/array` | | [`@helpers4/ci`](https://www.npmjs.com/package/@helpers4/ci) | CI/CD workflow status formatting for PR comments and pipeline reports | `npm i @helpers4/ci` | | [`@helpers4/color`](https://www.npmjs.com/package/@helpers4/color) | Color conversion and validation utilities | `npm i @helpers4/color` | | [`@helpers4/commit`](https://www.npmjs.com/package/@helpers4/commit) | Conventional Commits parsing, validation, and analysis | `npm i @helpers4/commit` | @@ -46,12 +46,14 @@ Install only what you need, or grab everything at once: | [`@helpers4/function`](https://www.npmjs.com/package/@helpers4/function) | Function composition and utility wrappers | `npm i @helpers4/function` | | [`@helpers4/guard`](https://www.npmjs.com/package/@helpers4/guard) | Runtime type guard utilities | `npm i @helpers4/guard` | | [`@helpers4/id`](https://www.npmjs.com/package/@helpers4/id) | Unique identifier generation (UUID v7, etc.) | `npm i @helpers4/id` | +| [`@helpers4/map`](https://www.npmjs.com/package/@helpers4/map) | Native `Map` manipulation utilities | `npm i @helpers4/map` | | [`@helpers4/markdown`](https://www.npmjs.com/package/@helpers4/markdown) | Markdown formatting and escaping utilities | `npm i @helpers4/markdown` | | [`@helpers4/node`](https://www.npmjs.com/package/@helpers4/node) | Node.js runtime utilities (Buffer, etc.) | `npm i @helpers4/node` | | [`@helpers4/number`](https://www.npmjs.com/package/@helpers4/number) | Numeric operations and formatting | `npm i @helpers4/number` | | [`@helpers4/object`](https://www.npmjs.com/package/@helpers4/object) | Object manipulation, deep comparison, and merging | `npm i @helpers4/object` | | [`@helpers4/observable`](https://www.npmjs.com/package/@helpers4/observable) | RxJS Observable helpers and operators | `npm i @helpers4/observable` | -| [`@helpers4/promise`](https://www.npmjs.com/package/@helpers4/promise) | Promise utilities, retry, delay, and error handling | `npm i @helpers4/promise` | +| [`@helpers4/promise`](https://www.npmjs.com/package/@helpers4/promise) | Promise utilities — retry, delay, concurrency limiting, mutex/semaphore, and error handling | `npm i @helpers4/promise` | +| [`@helpers4/set`](https://www.npmjs.com/package/@helpers4/set) | Native `Set` manipulation utilities | `npm i @helpers4/set` | | [`@helpers4/string`](https://www.npmjs.com/package/@helpers4/string) | String manipulation — capitalize, slugify, camelCase, etc. | `npm i @helpers4/string` | | [`@helpers4/type`](https://www.npmjs.com/package/@helpers4/type) | Compile-time TypeScript utility types | `npm i @helpers4/type` | | [`@helpers4/url`](https://www.npmjs.com/package/@helpers4/url) | URL parsing, manipulation, and normalization | `npm i @helpers4/url` | diff --git a/TODO.md b/TODO.md index bcd8437c..b688eecd 100644 --- a/TODO.md +++ b/TODO.md @@ -1,318 +1,100 @@ # TODO — `helpers4/typescript` -> Last refresh: 2026-07-19. +> Last refresh: 2026-07-21. Legend: 🔴 High priority · 🟡 Medium · 🟢 Low ---- - -## 1. v3 release readiness - -> Publishing a major version is a public, irreversible action — always confirm explicitly -> before triggering it, and only once the checklist below is clean. - -- [x] 🔴 Audit `@deprecated` tags before cutting v3 — checked (2026-07-14): zero matches across - `helpers/`, `scripts/`, and everything else except this TODO. Nothing to remove. -- [x] 🔴 Confirm CHANGELOG / migration notes cover all breaking changes since v2 — they didn't - (2026-07-14): the 6 breaking refactor commits (`type`→`guard` split + 5 removed deprecated - aliases) used prose like "Breaking change for v3." instead of the Conventional Commits - `BREAKING CHANGE:` footer, so git-cliff silently never flagged them. Fixed: added - [MIGRATION.md](MIGRATION.md) with a proper `BREAKING CHANGE:` footer commit, linked it from - `cliff.toml`'s header, and regenerated `CHANGELOG.md` (which was also stale since - 2.0.0-alpha.11 — every release through 2.1.0 plus all of v3 was missing from it). -- [x] 🟡 `v3.0.0-alpha.1`/`v3.0.0-alpha.2` git tags were **not ancestors** of `v3` (orphaned by - the rebase onto `main`). **Resolved (2026-07-15)**: `v3` merged into `main` directly (no PR — - confirmed via `gh pr list`). Both tags re-pointed from their orphaned commits to the matching - `chore(release): bump version to 3.0.0-alpha.N` commits now on `main` (verified identical - `package.json` version field before moving), re-signed, force-pushed. Both now correctly - resolve as ancestors of `main`. - **New finding while doing this**: the merge only carried `v3` up through the "resolve - orphaned-tags" commit — the entire two-round bug-fix code review session that followed - (24 commits, 7 real bugs: prototype pollution in `omitBy`/`flatten`, `DeepSet` empty-path type, - `parseDuration` misparsing `"500ms"`, `toggle()` NaN handling, `EpochMilliseconds` restoration, - `cloneDeep` silently losing Map/Set data, color-clamp fixes, plus the `@since` coherency check) - was never pushed and is **not on `main`**. Recovered into a local branch - (`v3-fixes-recovery`, not yet pushed) so nothing is lost — integrating it is a separate, - not-yet-decided follow-up. -- [x] 🔴 Publish v3 — done: v3.0.0 published, followed by v3.0.1 (2026-07-18). +Only open items live here. Anything finished is in git history / `CHANGELOG.md` / +`MIGRATION.md`, not duplicated in this file. --- -## 2. Making the library AI-friendly - -> Goal: an LLM/agent exploring or consuming this repo should get correct info fast, without -> parsing the whole codebase. "Promoting" the lib to AI communities isn't a task an agent can -> execute on the maintainer's behalf — a good `llms.txt` + structured docs *is* the promotion, -> agents that crawl the repo or the docs site will pick it up naturally. +## AI-friendliness / discovery -- [x] 🔴 Add an `llms.txt` at the repo root — package list, conventions, doc links - (2026-07-14). Turns out each package **already** ships its own much richer auto-generated - `llms.txt` (full signatures/params/examples from `meta/api.json`, see - `scripts/build/build-llms-txt.ts`) — the root one is complementary, for repo-level - orientation, not a replacement. Confirmed compliant with the llmstxt.org spec (only a H1 is - actually required; our blockquote + H2 link-list sections already follow the - `[name](url): notes` format). -- [x] 🟡 **`llms.txt` on helpers4.dev** — done (2026-07-19, `website` repo, `next-feat` branch, - not yet merged): hand-authored `public/llms.txt` at the site root, H2 per product (typescript, - devcontainer, action), linking to `/typescript/llms-full.txt` for the TS deep-dive instead of - duplicating it. That deep-dive file is `build/all/llms.txt` copied by - `generate-typescript-docs.js` into `public//llms-full.txt` on every doc-gen run - (one per version slot — `typescript/`, `typescript/next/`, archived `typescript/vN/` all get - their own snapshot; the major-version archiving step now carries its slot's copy along too). - Verified end-to-end: ran the generator for real, built the site, confirmed both files serve at - the right URLs with real content (316KB / 12356 lines for the TS deep-dive). -- [ ] 🟢 Submit to llms.txt discovery directories once the helpers4.dev one exists — - [llmstxt.site](https://llmstxt.site/submit) (form) and - [llms-txt-hub](https://github.com/thedaviddias/llms-txt-hub) (PR-based). Low cost, do after - the site-level file above. - [ ] 🟢 List helpers4 on general (non-AI) TypeScript discovery sites — the previously-suggested - target, [dzharii/awesome-typescript](https://github.com/dzharii/awesome-typescript), has been - **archived (read-only) since 2026-02-11** — verified via `gh repo view`, not a valid PR target - anymore. Needs a currently-maintained curated TS list found and confirmed active before - attempting a submission; none of the guessed alternative repo names existed. Aggregator sites - (libraries.io, npmtrends) still don't need submission, they index npm automatically. -- [ ] 🟢 Package `/add-helper` (or the not-yet-built consumer-facing skill) as a real Claude - Code **plugin** (`.claude-plugin/marketplace.json`) and submit to + target (`dzharii/awesome-typescript`) is archived/read-only as of 2026-02-11. Needs a + currently-maintained curated TS list found and confirmed active first. +- [ ] 🟢 Package `/add-helper` (or the consumer-facing skill below) as a real Claude Code plugin + and submit to [anthropics/claude-plugins-official](https://github.com/anthropics/claude-plugins-official) — - only makes sense for the consumer-facing skill (see item below); `/add-helper` is - project-scoped by nature and gains nothing from external listing since it only helps people - already working in this repo. -- [x] 🟡 Audit AI-agent config consistency across the workspace (2026-07-14) — found and fixed: - `.dev/AGENTS.md`'s "never git commit" rule read as absolute despite explicit per-turn - authorization being a normal thing to grant — clarified it's per-turn, not standing. - `typescript/AGENTS.md` had a factually wrong claim ("cross-package imports break - tree-shaking") contradicted by 20+ existing cross-category imports — fixed, and documented - the real `BREAKING CHANGE:` footer requirement for `git-cliff`. Auto-memory store was - completely empty despite months of session history — seeded with the findings above. -- [x] 🟡 Contributor-facing Claude Code skill — added `/add-helper` (2026-07-14, - `.claude/skills/add-helper/SKILL.md`): scaffolds a new helper's 4 files following - `CONTRIBUTING.md`, bakes in conventions that only live in `AGENTS.md`/session history - (naming: no lodash/math jargon, cross-category imports are safe, verification command order, - per-turn commit authorization). -- [ ] 🟢 **Consumer-facing Claude Code skill** (not built — needs a separate design pass): - unlike `/add-helper` (helps people contribute *to* this repo), this would help people *using* - `@helpers4/*` as a dependency in their own project avoid reinventing a utility that already - exists here. Sketch: - - Trigger: before writing a new small utility function (array/object/string/date manipulation, - a type guard, etc.), check whether `@helpers4/` already has it. - - Source of truth: the per-package `llms.txt` already generated at build time (see item above) - — no new data pipeline needed, just point the skill at it (fetched from npm/unpkg, or from - `node_modules/@helpers4//llms.txt` if the package is already installed). - - Distribution is the hard part, same issue as the "good first issue" outreach blocker: a - skill nobody installs has zero effect. Needs to ship through something with actual reach — - a Claude Code plugin/marketplace listing, or bundled as an optional install step in - `CONTRIBUTING.md`/README for people already using the lib — not just dropped in this repo's - `.claude/skills/` (which only helps people already working *in* this repo, i.e. the - `/add-helper` use case, not the "instead of writing my own debounce, use helpers4" use - case). - - Worth prototyping once there's an actual channel to distribute it through — building it - before that exists is the same mistake as opening good-first-issues before there was - traffic to see them. + only makes sense for the consumer-facing skill; `/add-helper` is project-scoped and gains + nothing from external listing. +- [ ] 🟢 **Consumer-facing Claude Code skill** (not built — needs a design pass): unlike + `/add-helper` (helps people contribute *to* this repo), this would help people *using* + `@helpers4/*` avoid reinventing a utility that already exists here. + - Source of truth: the per-package `llms.txt` already generated at build time — no new data + pipeline needed. + - Distribution is the hard part (same blocker as the outreach item below): needs an actual + channel — a plugin/marketplace listing, or a `CONTRIBUTING.md`/README install step — not just + dropped in this repo's `.claude/skills/`, which only reaches people already working *in* this + repo. Worth prototyping once that channel exists, not before. --- -## 3. 🫂 Wanted: contributors +## Contributors / outreach > Blocked while the project is solo. One regular collaborator unlocks the `Code-Review` > Scorecard check (~+2 pts) and makes human review possible. -- [x] 🔴 Open "good first issue" labelled issues to attract first-timers — **reconsidered twice - (2026-07-14)**: first drafted the missing-`.bench.ts` gap as an issue candidate, then realized - opening it was premature (nobody watches this repo's issue tracker yet, so the label attracts - no one without outreach happening first — sequence matters: outreach → traffic → issues). Since - there was nothing stopping *me* from just doing the mechanical work directly instead of waiting - for a first-timer, wrote the 98 missing benchmark files myself (97 candidates + `markdown/escape` - caught on a final audit pass) rather than leave it queued. Deliberately skipped `ci/*` (3), - `promise/*` (11), `observable/combine`+`combineLatest`+`isObservable` (3), and `version/stripV` - (1) — thin wrappers / trivial one-liners, per CONTRIBUTING.md's own "type guards and thin - wrappers do not need benchmarks" policy. All new files pass `typecheck`/`lint`, and every - category's full bench suite was run once to confirm no runtime errors. -- [ ] 🔴 Mention the project in TS communities (TypeScript Discord, Reddit r/typescript, - X/Twitter) — outreach, not something an agent can do on the maintainer's behalf. -- [x] 🔴 Write a clear `CONTRIBUTING.md` with devcontainer setup in 2 commands — added, pointing - at the sibling `helpers4/.dev` orchestration repo's devcontainer (2026-07-14). -- [x] 🔴 Add a "PRs welcome" badge to the README (2026-07-14). -- [ ] 🟡 Once a regular reviewer joins: raise `required_approving_review_count` to `1` in the - `main` ruleset, merge a few PRs with human approvals, record evidence per PR (URL, reviewer, - timestamp) — unlocks `Code-Review` scoring. +- [ ] 🔴 Mention the project across TS/JS communities — outreach, not something an agent can do + on the maintainer's behalf. Checked 2026-07-21, concrete targets: + - PR to [`sorrycc/awesome-javascript`](https://github.com/sorrycc/awesome-javascript) (active, + last push same day) — one entry, `[PACKAGE](LINK) - DESCRIPTION.` per its `CONTRIBUTING.md`. + Also covers **LibHunt** for free — it auto-indexes from this exact list, no separate + submission there. + - Post to r/typescript + r/javascript, Discord, X/Twitter. + - Write a "Why Helpers4?" article, cross-post to Dev.to + Hashnode + LinkedIn. + - Show HN — one-shot and timing-sensitive (can't really redo it), but real upside if the + article above gives it something to point to. + - **DevHunt** (dev-tool-focused Product Hunt alternative, PR-based submission) instead of + Product Hunt itself — Product Hunt expects a fresh-launch narrative that a library shipped + as v3 months ago doesn't have; DevHunt doesn't. + - Add relevant GitHub Topics tags to the repo (`typescript`, `utility-library`, …) — free, + self-service, no external submission. + - **Checked and rejected, don't re-research these**: Openbase (shut down 2023, domain dead), + Open Source Agenda (domain expired, now redirects off-site), npmcompare.com (unreachable), + "JS Toolbox" and "Perf-Track JS" (no such submittable sites exist), StackShare (built for + companies listing their stack, not devs promoting a library), BetaList (explicitly + pre-launch-only by their own criteria — disqualified), Indie Hackers (monetization-focused + community, OSS-lib posts are off-topic), Lobsters (invite-only, drive-by self-promo gets + flagged — only viable via genuine organic participation, not as a submission channel), + "Claude/Agent Skills Marketplace" (not a real distinct product, and out of scope anyway — + helpers4 is a TS library, not a Claude plugin). +- [ ] 🟡 Once a regular reviewer joins: raise `required_approving_review_count` to `1` on `main`, + record evidence (URL/reviewer/timestamp) per merged PR — unlocks `Code-Review` scoring. --- -## 4. OpenSSF Scorecard +## OpenSSF Scorecard -> Live score: check `curl -s https://api.securityscorecards.dev/projects/github.com/helpers4/typescript`. -> As of 2026-07-13: **7** (not 7.3 — corrected 2026-07-19, the API returns a plain integer, no -> decimal precision available). PRs #108/#109/#110 claim Branch-Protection, Token-Permissions, -> and Signed-Releases fixes, but this same 2026-07-13 scan still shows `Token-Permissions: 0` -> and `Branch-Protection: 3` (not maximal) — only Signed-Releases has been independently -> re-verified as actually fixed (see below). **Unverified**: whether Token-Permissions and -> Branch-Protection are genuinely fixed in the live repo config and just waiting on the same -> "scan predates the fix" rescan lag as Signed-Releases, or whether those two PRs didn't fully -> land — `gh api repos/helpers4/typescript/rulesets` returned `[]` (empty) when checked -> 2026-07-19, but the token used lacks admin rights to confirm that's a real absence of rules -> vs. a permissions gap in the check itself. Needs a maintainer check with an admin token before -> trusting either check's fix as confirmed. +> Live score: `curl -s https://api.securityscorecards.dev/projects/github.com/helpers4/typescript`. -- [x] 🟡 **Signed-Releases → target ≥ 10**: verified (2026-07-19) — `gh release view v3.0.1 --json - assets` confirms `build-meta.tar.gz.intoto.jsonl` is present on the release, so the provenance - mechanism works as intended. The live Scorecard score is still showing `Signed-Releases: 0`, - but that scan is dated 2026-07-13, **before** v3.0.0/v3.0.1 existed — it hasn't rescanned since. - Nothing left to fix; just re-check `https://api.securityscorecards.dev/projects/github.com/helpers4/typescript` - after the next scan (Scorecard rescans periodically; no manual trigger available for public repos). -- [ ] 🟢 **CII-Best-Practices Silver/Gold** — deferred, requires sustained contributor activity - (currently solo project). +- [ ] 🟡 Token-Permissions and Branch-Protection fixes (PRs #108/#109) are unverified against the + live repo config — `gh api repos/helpers4/typescript/rulesets` returned `[]` on 2026-07-19, but + the token used lacks admin rights to tell "no rules" from "no permission to see them." Needs a + maintainer check with an admin token. +- [ ] 🟢 CII-Best-Practices Silver/Gold — deferred, needs sustained contributor activity. --- -## 5. Code quality & tooling +## Code quality & tooling -- [x] 🟢 Improve mutation score — checked (2026-07-14): **92.5%**, already high. Downgraded to - low priority — chasing the remaining ~7.5% is diminishing returns until the - [Stryker dashboard](https://dashboard.stryker-mutator.io/reports/github.com/helpers4/typescript/main) - is checked for a specific weak file worth targeting. -- [x] 🟡 Verify browser compatibility — checked (2026-07-14): no Node-only APIs leak outside - `helpers/node/` (the 2 grep hits on `Buffer`/`process` were a JSDoc word and a string-literal - constructor-name check — both browser-safe). **But found a real gap**: `package.json`'s - `runtimes.browser: "ES2022+"` claim doesn't hold for the `Temporal`-based date helpers - (`ensureDate`, `range`, `timeAgo`, `formatDuration`, `timezone` + 6 `guard/isTemporal*` - guards) — `Temporal` is not part of the ES2022 spec, ships zero-dependency (no polyfill), and - as of 2026-07-14 has ~64% global coverage: Chrome 144+ and Firefox 139+ support it natively, - but **Safari has no stable support** (behind a flag in Technical Preview only; WebKit signals - full support "late 2026"). CI only tests Node/Deno/Bun, never an actual browser. **Needs a - maintainer decision, not a silent fix**: document the Safari caveat next to the `browser` - badge/claim, scope it down (e.g. `"browser: Chrome 144+, Firefox 139+"`), or accept a - polyfill as an optional peer dependency for the `date`/`guard` packages specifically. - ([caniuse](https://caniuse.com/temporal), [web-features](https://web-platform-dx.github.io/web-features-explorer/features/temporal/)) +- [ ] 🟡 **Browser compatibility gap**: the `Temporal`-based date helpers (`ensureDate`, `range`, + `timeAgo`, `formatDuration`, `timezone` + 6 `guard/isTemporal*`) claim `ES2022+` support but + `Temporal` isn't in ES2022 and **Safari has no stable support** yet (WebKit signals "late + 2026"). CI never tests an actual browser. **Needs a maintainer decision**: document the Safari + caveat, scope the `browser` claim down, or accept a polyfill as an optional peer dependency for + `date`/`guard`. +- [ ] 🟡 **`.d.ts` emit blocked** on TypeScript 7: `rollup-plugin-dts` needs the classic Compiler + API, which TS 7.0's main export no longer exposes at all outside `unstable/*`. Tracked upstream + at [Swatinem/rollup-plugin-dts#395](https://github.com/Swatinem/rollup-plugin-dts/issues/395) + (maintainer has a working fix on a branch, not yet merged). Revisit once that ships, or once + TS 7.1 delivers a stable new API. --- -## 6. Future / needs design discussion - -- [ ] 🟢 New "settings" package for shared tool configs (vite, vitest, oxc, lint, cspell, …), - customized for helpers4/baxyz instead of depending on a third-party preset (airbnb, angular…). - **Not a `helpers/` category** — configs don't compile/tree-shake like functions, so this - should be a separate package or sibling repo, not a folder under `helpers/`. Needs a design - decision before implementation. -- [x] 🟡 **`typecheck` migrated to real TypeScript 7** (2026-07-20) — TS 7.0 shipped stable - 2026-07-08, as `typescript@latest` (7.0.2) itself; `@typescript/native-preview` (the old - `tsgo`-named preview package we depended on) is now obsolete, frozen on a dev-build predating - the stable release. Switched via npm aliasing: `"@typescript/native": "npm:typescript@^7.0.2"` - (fast native compiler, bin `tsc`) plus `"typescript": "npm:@typescript/typescript6@^6.0.2"` - (classic-API compatibility package, bin `tsc6`, so anything doing `require('typescript')` — - i.e. `rollup-plugin-dts` — keeps working unchanged). `typecheck` script updated from - `tsgo --noEmit` to `tsc --noEmit` (bin renamed back to `tsc` in the stable release). - **Build/`.d.ts` emit still blocked, and it's a different blocker than previously thought**: - our `.d.ts` bundling goes through `rollup-plugin-dts`, which depends on the classic TS - Compiler API (`ts.createProgram` etc.) — and TypeScript 7.0's main package export is now - *only* `version`/`versionMajorMinor` (verified directly: `require('typescript@7.0.2').createProgram` - is `undefined`), no compiler API at all outside explicitly-`unstable/*` subpaths. Not - something we can fix from this repo — tracked upstream at - [Swatinem/rollup-plugin-dts#395](https://github.com/Swatinem/rollup-plugin-dts/issues/395) - (open; maintainer has a working fix on a branch using the same `@typescript/typescript6` - alias pattern, not yet merged/released). Revisit once that ships, or once TS 7.1 (already in - `next` as of today) delivers the promised new stable API. - ---- - -## 7. Competitive gap analysis (2026-07-19) - -> Researched which lodash/ramda-style competitors are still actively maintained, beyond -> radashi/remeda (already in `website`'s comparisons). Found: **es-toolkit** (Toss-backed, very -> active), **rambda** (TS-native Ramda alternative), **moderndash** (smaller, newer). Full -> writeups live in the `website` repo's `comparisons/` pages — this section is the "what's -> actually missing from helpers4" distillation, prioritized. `@mobily/ts-belt` checked and -> excluded (last published 2023-01-10, not active). Effect-TS excluded — different category -> (FP platform/runtime, not a utility library). +## Future / needs design discussion -- [x] 🔴 **`Map`/`Set` utilities** (2026-07-19, revised same day after checking against native - JS) — added as two separate categories, not a combined `@helpers4/collection`: the repo's own - precedent (`array`/`object` share `compact`/`equalsShallow` **on purpose**, documented in - `CONTRIBUTING.md`'s "Intentional cross-category duplicates") already answers "one category - per type vs. one for the operation-kind" in favor of per-type, and `guard`/`type` (the - operation-kind categories) don't fit Map/Set's shape. - `@helpers4/map` (11 functions): `filter`, `reduce`, `some`, `every`, `countBy`, `toMapByKey`, - `findKey`, `findValue`, `hasValue`, `mapKeys`, `mapValues`. - `@helpers4/set` (4 functions): `filter`, `countBy`, `toMapByKey`, `map`. - (`keyBy` renamed to `toMapByKey` after review — lodash's `_.keyBy` returns a plain object, - not a `Map`, so the bare name risked exactly that wrong assumption; the `to` prefix - disambiguates the same way `toSorted`/`toReversed` do.) - **Checked against native JS on the actual Node 26 baseline before finalizing** (not just - assumed) — dropped 6 of the originally-planned functions as genuine duplicates: - `map.forEach`/`set.forEach` (native `Map`/`Set.prototype.forEach` already exist, identical - signature) and `set.reduce`/`some`/`every`/`find` (native since Node 22's Iterator Helpers — - `set.values().reduce/some/every/find(fn)` — and unlike `Map`, `Set` already iterates plain - values with no tuple to destructure, so there was no real convenience left to add). Kept - `map`'s `reduce`/`some`/`every`/`findKey`/`findValue`/`hasValue` — those *do* still save a - `[key, value]` tuple destructure over the native `map.entries().foo()` equivalent. Documented - all of this (including what's *not* implemented and why) in `docs/native-alternatives.json`'s - new `map`/`set` sections, consumed by the website build. -- [x] 🟡 **Number statistics: `median`, `percentile`, `meanBy`, `sumBy`** (2026-07-19) — added to - `@helpers4/array` alongside the existing `mean`/`sum`. `percentile` uses linear interpolation - between closest ranks (so `percentile(arr, 50)` matches `median`). -- [x] 🟢 **Bulk object-key case transforms** (2026-07-19, revised same day after review) — landed - as **5** functions, not 2: `camelCaseKeys`, `snakeCaseKeys`, `kebabCaseKeys`, `pascalCaseKeys`, - `titleCaseKeys` (renamed from the original `toCamelCaseKeys`/`toSnakeCaseKeys` — dropped the - `to` prefix to match `@helpers4/string`'s own naming: `camelCase`/`snakeCase`/`kebabCase`, no - `to`). Also added `@helpers4/object/mapDeep` — the recursive sibling to the existing `map` - (same shallow/deep pairing convention as `clone`/`cloneDeep`) — and rebuilt all 5 `*Keys` - functions as thin wrappers over it instead of each hand-rolling its own recursion. - Found along the way: `@helpers4/string`'s `camelCase`/`kebabCase` are narrower than their - siblings — `camelCase` only handles kebab-case input, `kebabCase` only handles - camelCase/PascalCase input, unlike `snakeCase`/`pascalCase`/`titleCase` which all handle any - input format. `snakeCaseKeys`/`pascalCaseKeys`/`titleCaseKeys` reuse those public (already - robust) functions directly; `camelCaseKeys`/`kebabCaseKeys` use a local, equally-robust - `words()`-based helper instead (`object/_caseKeysHelpers.ts`) so they work correctly regardless - of whether/when `camelCase`/`kebabCase` themselves get fixed — see the follow-up item below. - Also added `sortKeys` (shallow) as a closely-related function while in this area. -- [x] 🟡 **Fixed `@helpers4/string`'s `camelCase`/`kebabCase` narrowness** (2026-07-19) — both - now tokenize the same way `snakeCase`/`pascalCase`/`titleCase` already did (handle - snake_case/kebab-case/PascalCase/spaces, not just one specific input format each). Real - **`BREAKING CHANGE`** to shipped functions (`@since 1.9.0`) — e.g. `camelCase('-leading')` - now returns `'leading'`, not `'Leading'`; `camelCase('UPPER-CASE')` now returns `'upperCase'`, - not `'UPPER-CASE'` unchanged. `camelCaseKeys`/`kebabCaseKeys` dropped their local - `_caseKeysHelpers.ts` workaround (deleted) and now delegate to the public functions directly, - same as the other three `*Keys` variants. -- [x] 🟢 **Environment/JSON guards** (2026-07-19) — added `isBrowser`, `isNode`, `isLength`, - `isJSONValue`, `isJSONArray`, `isJSONObject` to `@helpers4/guard`. `isJSON` ended up with a - more useful, distinct meaning than a plain alias: checks whether a **string** is valid - parseable JSON (pairs with `@helpers4/object`'s `safeJsonParse`), rather than duplicating - `isJSONValue`'s already-parsed-value shape check. -- [x] 🟡 **Concurrency primitives in `@helpers4/promise`** (2026-07-20) — added `createSemaphore` - (limits concurrent holders to `permits`, FIFO-queueing excess `acquire()` callers) and - `createMutex` (a semaphore with a single permit, for mutual exclusion — internally just - `createSemaphore(1)` with a renamed `isLocked()` accessor instead of `available()`). Kept - `delay`/`timeout`/`withTimeout` as-is — those already existed (the original gap-analysis entry - was wrong on them, see below). - Factory functions returning a closure-backed object, not `export class` — matches this - package's other stateful helpers (`debounce`, `throttle`), so this isn't actually the "first - stateful API" the original entry worried it'd be. `acquire()` resolves to a one-shot release - function (not a bare `release(): void`) plus a `run(fn)` convenience that acquires, runs, and - releases even if `fn` throws (mirrors `defer`'s try/finally guarantee) — `run()` is the - documented preferred way to use either. **Revised (2026-07-21, code review)**: a bare shared - `release()` counter can't tell a legitimate release from a double-release once there's - contention — verified directly that a stray extra `release()` call could silently hand a - phantom permit to an unrelated queued waiter, breaking mutual exclusion. Fixed by having - `acquire()` resolve to a per-acquisition release function with its own already-released - state, so a double-release is always caught, not just when the semaphore is fully idle. -- [x] 🟢 **Async-aware array iteration** (2026-07-20) — added `mapAsync`/`filterAsync`/ - `forEachAsync` to `@helpers4/array`, mirroring `Array.prototype.map`/`filter`/`forEach`'s - calling convention (`(item, index) => ...`) directly against the array, instead of requiring - callers to pre-wrap each item as a `() => Promise` thunk for `parallel`. `concurrency` is - optional (unlike `parallel`'s required `limit`) — omitted means unlimited, matching - `Promise.all(array.map(fn))`; `0`/negative/`NaN` throw `RangeError` rather than silently - falling back to a default, unlike `parallel`'s existing clamp-to-1 behavior (a deliberate, - stricter contract for this new, optional parameter — not a change to `parallel` itself). - Rejects on the first error, same as `Promise.all`/`parallel` (no settle/partition variant — - use `parallelSettle` directly for that). Shared queueing logic lives in an in-category - internal helper, `array/_concurrentMap.ts` (matches the `_sortHelpers.ts`/`_byAccessor.ts` - convention), used by all three. - **Revised (2026-07-21, code review)**: `runConcurrentMap` (used by `mapAsync`/`filterAsync`) - now delegates its actual scheduling to `promise/parallel` instead of a second, independent - worker-pool implementation — `concurrency` is validated/clamped first, so `parallel`'s own - (more permissive) clamping never comes into play and its behavior for direct callers is - unaffected. `forEachAsync` keeps its own lean loop via `runConcurrentEach`, since it has no - use for `parallel`'s results array. Also found and fixed: the same Infinity-clamped-to-1 bug - this entry's `parallel` comparison was written against also existed in `parallelSettle` — - fixed there too. The "positive count, floor non-integer, throw on invalid" validation is now - shared (`_shared/_validatePositiveCount.ts`) between `createSemaphore` and this file instead - of two near-identical inline checks. +- [ ] 🟢 New "settings" package for shared tool configs (vite/vitest/oxc/lint/cspell…), customized + for helpers4/baxyz instead of a third-party preset. Not a `helpers/` category — configs don't + tree-shake like functions — needs a design decision on package/repo shape before implementation. diff --git a/helpers/_shared/_unsafeKeys.ts b/helpers/_shared/_unsafeKeys.ts index 0ac808c9..56d73172 100644 --- a/helpers/_shared/_unsafeKeys.ts +++ b/helpers/_shared/_unsafeKeys.ts @@ -13,4 +13,4 @@ const UNSAFE_KEY_LIST = ['__proto__', 'constructor', 'prototype'] as const; /** @ignore */ export type UnsafeKey = (typeof UNSAFE_KEY_LIST)[number]; -export const UNSAFE_KEYS = new Set(UNSAFE_KEY_LIST); +export const UNSAFE_KEYS = /* @__PURE__ */ new Set(UNSAFE_KEY_LIST); diff --git a/helpers/array/filterAsync.bench.ts b/helpers/array/filterAsync.bench.ts new file mode 100644 index 00000000..f89c838b --- /dev/null +++ b/helpers/array/filterAsync.bench.ts @@ -0,0 +1,23 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { filterAsync } from './filterAsync'; + +const small = Array.from({ length: 10 }, (_, i) => i); +const large = Array.from({ length: 1_000 }, (_, i) => i); + +describe('filterAsync', () => { + bench('small array, unlimited concurrency', async () => { + await filterAsync(small, (n) => n % 2 === 0); + }); + bench('large array, unlimited concurrency', async () => { + await filterAsync(large, (n) => n % 2 === 0); + }); + bench('large array, concurrency capped at 10', async () => { + await filterAsync(large, (n) => n % 2 === 0, 10); + }); +}); diff --git a/helpers/array/forEachAsync.bench.ts b/helpers/array/forEachAsync.bench.ts new file mode 100644 index 00000000..55abd695 --- /dev/null +++ b/helpers/array/forEachAsync.bench.ts @@ -0,0 +1,23 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { forEachAsync } from './forEachAsync'; + +const small = Array.from({ length: 10 }, (_, i) => i); +const large = Array.from({ length: 1_000 }, (_, i) => i); + +describe('forEachAsync', () => { + bench('small array, unlimited concurrency', async () => { + await forEachAsync(small, () => {}); + }); + bench('large array, unlimited concurrency', async () => { + await forEachAsync(large, () => {}); + }); + bench('large array, concurrency capped at 10', async () => { + await forEachAsync(large, () => {}, 10); + }); +}); diff --git a/helpers/array/mapAsync.bench.ts b/helpers/array/mapAsync.bench.ts new file mode 100644 index 00000000..47d4ce92 --- /dev/null +++ b/helpers/array/mapAsync.bench.ts @@ -0,0 +1,23 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { mapAsync } from './mapAsync'; + +const small = Array.from({ length: 10 }, (_, i) => i); +const large = Array.from({ length: 1_000 }, (_, i) => i); + +describe('mapAsync', () => { + bench('small array, unlimited concurrency', async () => { + await mapAsync(small, (n) => n * 2); + }); + bench('large array, unlimited concurrency', async () => { + await mapAsync(large, (n) => n * 2); + }); + bench('large array, concurrency capped at 10', async () => { + await mapAsync(large, (n) => n * 2, 10); + }); +}); diff --git a/helpers/array/meanBy.bench.ts b/helpers/array/meanBy.bench.ts new file mode 100644 index 00000000..4eee34e5 --- /dev/null +++ b/helpers/array/meanBy.bench.ts @@ -0,0 +1,26 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { meanBy } from './meanBy'; + +const small = [{ price: 10 }, { price: 20 }, { price: 30 }]; +const large = Array.from({ length: 10_000 }, (_, i) => ({ price: i })); + +describe('meanBy', () => { + bench('small array, function accessor', () => { + meanBy(small, (item) => item.price); + }); + bench('large array, function accessor', () => { + meanBy(large, (item) => item.price); + }); + bench('large array, string path accessor', () => { + meanBy(large, 'price'); + }); + bench('null input', () => { + meanBy(null, (item: { price: number }) => item.price); + }); +}); diff --git a/helpers/array/median.bench.ts b/helpers/array/median.bench.ts new file mode 100644 index 00000000..8f3c8929 --- /dev/null +++ b/helpers/array/median.bench.ts @@ -0,0 +1,20 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { median } from './median'; + +const small = [5, 3, 1, 4, 2]; +const large = Array.from({ length: 10_000 }, () => Math.random()); + +describe('median', () => { + bench('small array (odd length)', () => { + median(small); + }); + bench('large array', () => { + median(large); + }); +}); diff --git a/helpers/array/percentile.bench.ts b/helpers/array/percentile.bench.ts new file mode 100644 index 00000000..a3630792 --- /dev/null +++ b/helpers/array/percentile.bench.ts @@ -0,0 +1,23 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { percentile } from './percentile'; + +const small = [5, 3, 1, 4, 2]; +const large = Array.from({ length: 10_000 }, () => Math.random()); + +describe('percentile', () => { + bench('small array, p50', () => { + percentile(small, 50); + }); + bench('large array, p50 (interpolated)', () => { + percentile(large, 50); + }); + bench('large array, p0 (min, still sorts first)', () => { + percentile(large, 0); + }); +}); diff --git a/helpers/array/sumBy.bench.ts b/helpers/array/sumBy.bench.ts new file mode 100644 index 00000000..8b3c8745 --- /dev/null +++ b/helpers/array/sumBy.bench.ts @@ -0,0 +1,29 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { sumBy } from './sumBy'; + +const small = [{ price: 10 }, { price: 20 }, { price: 30 }]; +const large = Array.from({ length: 10_000 }, (_, i) => ({ price: i, nested: { value: i } })); + +describe('sumBy', () => { + bench('small array, function accessor', () => { + sumBy(small, (item) => item.price); + }); + bench('large array, function accessor', () => { + sumBy(large, (item) => item.price); + }); + bench('large array, string path accessor', () => { + sumBy(large, 'price'); + }); + bench('large array, nested string path accessor', () => { + sumBy(large, 'nested.value'); + }); + bench('null input', () => { + sumBy(null, (item: { price: number }) => item.price); + }); +}); diff --git a/helpers/color/hexToRgb.ts b/helpers/color/hexToRgb.ts index 82154705..b6173ca9 100644 --- a/helpers/color/hexToRgb.ts +++ b/helpers/color/hexToRgb.ts @@ -22,7 +22,7 @@ export interface RgbColor { a?: number; } -const HEX_PATTERN = new RegExp(`^#?(${HEX_COLOR_DIGITS})$`, 'i'); +const HEX_PATTERN = /* @__PURE__ */ new RegExp(`^#?(${HEX_COLOR_DIGITS})$`, 'i'); function expandShortHex(digits: string): string { return digits.length <= 4 diff --git a/helpers/guard/isCssColor.ts b/helpers/guard/isCssColor.ts index 4d4580f4..c167d50d 100644 --- a/helpers/guard/isCssColor.ts +++ b/helpers/guard/isCssColor.ts @@ -6,7 +6,7 @@ import { HEX_COLOR_DIGITS } from '../_shared/_hexColorGrammar.js'; -const HEX_COLOR = new RegExp(`^#(?:${HEX_COLOR_DIGITS})$`, 'i'); +const HEX_COLOR = /* @__PURE__ */ new RegExp(`^#(?:${HEX_COLOR_DIGITS})$`, 'i'); const FUNCTIONAL_COLOR = /^(?:rgb|rgba|hsl|hsla)\(\s*[\d.%,\s/-]+\s*\)$/i; const NAMED_COLOR = /^[a-z]+$/i; diff --git a/helpers/map/countBy.bench.ts b/helpers/map/countBy.bench.ts new file mode 100644 index 00000000..a0c5d7a5 --- /dev/null +++ b/helpers/map/countBy.bench.ts @@ -0,0 +1,27 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { countBy } from './countBy'; + +const small = new Map([ + ['a', 1], + ['b', 2], + ['c', 3], +]); +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('countBy', () => { + bench('small map, few groups', () => { + countBy(small, (value) => (value % 2 === 0 ? 'even' : 'odd')); + }); + bench('large map, few groups', () => { + countBy(large, (value) => (value % 2 === 0 ? 'even' : 'odd')); + }); + bench('large map, many groups', () => { + countBy(large, (value) => value % 1000); + }); +}); diff --git a/helpers/map/every.bench.ts b/helpers/map/every.bench.ts new file mode 100644 index 00000000..0a0bedd0 --- /dev/null +++ b/helpers/map/every.bench.ts @@ -0,0 +1,27 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { every } from './every'; + +const small = new Map([ + ['a', 1], + ['b', 2], + ['c', 3], +]); +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('every', () => { + bench('small map, all match', () => { + every(small, (value) => value > 0); + }); + bench('large map, all match (worst case, full scan)', () => { + every(large, (value) => value >= 0); + }); + bench('large map, first entry fails (short-circuit)', () => { + every(large, (value) => value > 0); + }); +}); diff --git a/helpers/map/filter.bench.ts b/helpers/map/filter.bench.ts new file mode 100644 index 00000000..73620ff6 --- /dev/null +++ b/helpers/map/filter.bench.ts @@ -0,0 +1,24 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { filter } from './filter'; + +const small = new Map([ + ['a', 1], + ['b', 2], + ['c', 3], +]); +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('filter', () => { + bench('small map', () => { + filter(small, (value) => value % 2 === 0); + }); + bench('large map, half match', () => { + filter(large, (value) => value % 2 === 0); + }); +}); diff --git a/helpers/map/findKey.bench.ts b/helpers/map/findKey.bench.ts new file mode 100644 index 00000000..cb9e66cc --- /dev/null +++ b/helpers/map/findKey.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { findKey } from './findKey'; + +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('findKey', () => { + bench('large map, match near the start', () => { + findKey(large, (value) => value === 5); + }); + bench('large map, match near the end', () => { + findKey(large, (value) => value === 9_995); + }); + bench('large map, no match (full scan)', () => { + findKey(large, (value) => value === -1); + }); +}); diff --git a/helpers/map/findValue.bench.ts b/helpers/map/findValue.bench.ts new file mode 100644 index 00000000..646ec597 --- /dev/null +++ b/helpers/map/findValue.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { findValue } from './findValue'; + +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('findValue', () => { + bench('large map, match near the start', () => { + findValue(large, (value) => value === 5); + }); + bench('large map, match near the end', () => { + findValue(large, (value) => value === 9_995); + }); + bench('large map, no match (full scan)', () => { + findValue(large, (value) => value === -1); + }); +}); diff --git a/helpers/map/hasValue.bench.ts b/helpers/map/hasValue.bench.ts new file mode 100644 index 00000000..ae6b39b7 --- /dev/null +++ b/helpers/map/hasValue.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { hasValue } from './hasValue'; + +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('hasValue', () => { + bench('large map, value near the start', () => { + hasValue(large, 5); + }); + bench('large map, value near the end', () => { + hasValue(large, 9_995); + }); + bench('large map, value absent (full scan)', () => { + hasValue(large, -1); + }); +}); diff --git a/helpers/map/mapKeys.bench.ts b/helpers/map/mapKeys.bench.ts new file mode 100644 index 00000000..92b73b9d --- /dev/null +++ b/helpers/map/mapKeys.bench.ts @@ -0,0 +1,24 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { mapKeys } from './mapKeys'; + +const small = new Map([ + ['a', 1], + ['b', 2], + ['c', 3], +]); +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('mapKeys', () => { + bench('small map', () => { + mapKeys(small, (key) => key.toUpperCase()); + }); + bench('large map', () => { + mapKeys(large, (key) => key * 2); + }); +}); diff --git a/helpers/map/mapValues.bench.ts b/helpers/map/mapValues.bench.ts new file mode 100644 index 00000000..a78e5728 --- /dev/null +++ b/helpers/map/mapValues.bench.ts @@ -0,0 +1,24 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { mapValues } from './mapValues'; + +const small = new Map([ + ['a', 1], + ['b', 2], + ['c', 3], +]); +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('mapValues', () => { + bench('small map', () => { + mapValues(small, (value) => value * 10); + }); + bench('large map', () => { + mapValues(large, (value) => value * 10); + }); +}); diff --git a/helpers/map/reduce.bench.ts b/helpers/map/reduce.bench.ts new file mode 100644 index 00000000..65fe6530 --- /dev/null +++ b/helpers/map/reduce.bench.ts @@ -0,0 +1,24 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { reduce } from './reduce'; + +const small = new Map([ + ['a', 1], + ['b', 2], + ['c', 3], +]); +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('reduce', () => { + bench('small map', () => { + reduce(small, (acc, value) => acc + value, 0); + }); + bench('large map', () => { + reduce(large, (acc, value) => acc + value, 0); + }); +}); diff --git a/helpers/map/some.bench.ts b/helpers/map/some.bench.ts new file mode 100644 index 00000000..ef35d29c --- /dev/null +++ b/helpers/map/some.bench.ts @@ -0,0 +1,27 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { some } from './some'; + +const small = new Map([ + ['a', 1], + ['b', 2], + ['c', 3], +]); +const large = new Map(Array.from({ length: 10_000 }, (_, i) => [i, i])); + +describe('some', () => { + bench('small map, no match', () => { + some(small, (value) => value > 100); + }); + bench('large map, match near the start (short-circuit)', () => { + some(large, (value) => value === 5); + }); + bench('large map, no match (full scan)', () => { + some(large, (value) => value === -1); + }); +}); diff --git a/helpers/map/toMapByKey.bench.ts b/helpers/map/toMapByKey.bench.ts new file mode 100644 index 00000000..09f86755 --- /dev/null +++ b/helpers/map/toMapByKey.bench.ts @@ -0,0 +1,27 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { toMapByKey } from './toMapByKey'; + +const small = [ + { id: 'a', n: 1 }, + { id: 'b', n: 2 }, + { id: 'c', n: 3 }, +]; +const large = Array.from({ length: 10_000 }, (_, i) => ({ id: i, n: i * 2 })); + +describe('toMapByKey', () => { + bench('small array', () => { + toMapByKey(small, (item) => item.id); + }); + bench('large array', () => { + toMapByKey(large, (item) => item.id); + }); + bench('large array, with duplicate keys (collapsing)', () => { + toMapByKey(large, (item) => item.id % 100); + }); +}); diff --git a/helpers/object/camelCaseKeys.bench.ts b/helpers/object/camelCaseKeys.bench.ts new file mode 100644 index 00000000..80612dca --- /dev/null +++ b/helpers/object/camelCaseKeys.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { camelCaseKeys } from './camelCaseKeys'; + +const small = { user_name: 'Alice', home_address: { zip_code: '12345' } }; +const large = { + items: Array.from({ length: 100 }, (_, i) => ({ item_id: i, item_value: i * 2 })), +}; + +describe('camelCaseKeys', () => { + bench('small nested object', () => { + camelCaseKeys(small); + }); + bench('large array of snake_case objects (100 items)', () => { + camelCaseKeys(large); + }); +}); diff --git a/helpers/object/kebabCaseKeys.bench.ts b/helpers/object/kebabCaseKeys.bench.ts new file mode 100644 index 00000000..b5384ff4 --- /dev/null +++ b/helpers/object/kebabCaseKeys.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { kebabCaseKeys } from './kebabCaseKeys'; + +const small = { userName: 'Alice', homeAddress: { zipCode: '12345' } }; +const large = { + items: Array.from({ length: 100 }, (_, i) => ({ itemId: i, itemValue: i * 2 })), +}; + +describe('kebabCaseKeys', () => { + bench('small nested object', () => { + kebabCaseKeys(small); + }); + bench('large array of camelCase objects (100 items)', () => { + kebabCaseKeys(large); + }); +}); diff --git a/helpers/object/mapDeep.bench.ts b/helpers/object/mapDeep.bench.ts new file mode 100644 index 00000000..c0784f67 --- /dev/null +++ b/helpers/object/mapDeep.bench.ts @@ -0,0 +1,37 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { mapDeep } from './mapDeep'; + +const shallow = { a: 1, b: 2, c: 3 }; +const nested = { a: 1, b: { c: 2, d: { e: 3 } }, f: [1, 2, 3] }; +const large = { + items: Array.from({ length: 100 }, (_, i) => ({ id: i, value: i * 2 })), +}; +const upper = (key: string) => key.toUpperCase(); +const double = (value: unknown) => (typeof value === 'number' ? value * 2 : value); + +describe('mapDeep', () => { + bench('shallow object, mapValue only', () => { + mapDeep(shallow, double); + }); + bench('nested object (3 levels), mapValue only', () => { + mapDeep(nested, double); + }); + bench('nested object, mapKey only', () => { + mapDeep(nested, undefined, upper); + }); + bench('nested object, both mapValue and mapKey', () => { + mapDeep(nested, double, upper); + }); + bench('large array of objects (100 items)', () => { + mapDeep(large, double); + }); + bench('identity (no callbacks)', () => { + mapDeep(nested); + }); +}); diff --git a/helpers/object/parsePropertyPath.ts b/helpers/object/parsePropertyPath.ts index b1605ef0..5a862a0d 100644 --- a/helpers/object/parsePropertyPath.ts +++ b/helpers/object/parsePropertyPath.ts @@ -12,7 +12,7 @@ const BRACKET = /\[(\d+)\]/g; // call is pure waste. Capped so a caller who *does* build many distinct, // dynamic path strings can't grow this into an unbounded memory leak. const MAX_CACHE_SIZE = 500; -const pathCache = new Map(); +const pathCache = /* @__PURE__ */ new Map(); /** * Parses a dot/bracket-notation property path into an array of string/number diff --git a/helpers/object/pascalCaseKeys.bench.ts b/helpers/object/pascalCaseKeys.bench.ts new file mode 100644 index 00000000..9e8a8690 --- /dev/null +++ b/helpers/object/pascalCaseKeys.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { pascalCaseKeys } from './pascalCaseKeys'; + +const small = { user_name: 'Alice', home_address: { zip_code: '12345' } }; +const large = { + items: Array.from({ length: 100 }, (_, i) => ({ item_id: i, item_value: i * 2 })), +}; + +describe('pascalCaseKeys', () => { + bench('small nested object', () => { + pascalCaseKeys(small); + }); + bench('large array of snake_case objects (100 items)', () => { + pascalCaseKeys(large); + }); +}); diff --git a/helpers/object/snakeCaseKeys.bench.ts b/helpers/object/snakeCaseKeys.bench.ts new file mode 100644 index 00000000..6d9952e2 --- /dev/null +++ b/helpers/object/snakeCaseKeys.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { snakeCaseKeys } from './snakeCaseKeys'; + +const small = { userName: 'Alice', homeAddress: { zipCode: '12345' } }; +const large = { + items: Array.from({ length: 100 }, (_, i) => ({ itemId: i, itemValue: i * 2 })), +}; + +describe('snakeCaseKeys', () => { + bench('small nested object', () => { + snakeCaseKeys(small); + }); + bench('large array of camelCase objects (100 items)', () => { + snakeCaseKeys(large); + }); +}); diff --git a/helpers/object/sortKeys.bench.ts b/helpers/object/sortKeys.bench.ts new file mode 100644 index 00000000..7d9f9eb2 --- /dev/null +++ b/helpers/object/sortKeys.bench.ts @@ -0,0 +1,25 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { sortKeys } from './sortKeys'; + +const small = { c: 3, a: 1, b: 2 }; +const large = Object.fromEntries( + Array.from({ length: 1_000 }, (_, i) => [`key${999 - i}`, i]), +); + +describe('sortKeys', () => { + bench('small object', () => { + sortKeys(small); + }); + bench('large object (1000 keys), default order', () => { + sortKeys(large); + }); + bench('large object, custom comparator', () => { + sortKeys(large, (a, b) => b.localeCompare(a)); + }); +}); diff --git a/helpers/object/titleCaseKeys.bench.ts b/helpers/object/titleCaseKeys.bench.ts new file mode 100644 index 00000000..d1bcf2f5 --- /dev/null +++ b/helpers/object/titleCaseKeys.bench.ts @@ -0,0 +1,22 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { titleCaseKeys } from './titleCaseKeys'; + +const small = { user_name: 'Alice', home_address: { zip_code: '12345' } }; +const large = { + items: Array.from({ length: 100 }, (_, i) => ({ item_id: i, item_value: i * 2 })), +}; + +describe('titleCaseKeys', () => { + bench('small nested object', () => { + titleCaseKeys(small); + }); + bench('large array of snake_case objects (100 items)', () => { + titleCaseKeys(large); + }); +}); diff --git a/helpers/promise/createMutex.bench.ts b/helpers/promise/createMutex.bench.ts new file mode 100644 index 00000000..a2bf17bc --- /dev/null +++ b/helpers/promise/createMutex.bench.ts @@ -0,0 +1,27 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { createMutex } from './createMutex'; + +const noop = (): number => 42; + +describe('createMutex', () => { + bench('create a mutex', () => { + createMutex(); + }); + + bench('acquire + release, uncontended', async () => { + const mutex = createMutex(); + const release = await mutex.acquire(); + release(); + }); + + bench('run(), uncontended', async () => { + const mutex = createMutex(); + await mutex.run(noop); + }); +}); diff --git a/helpers/promise/createSemaphore.bench.ts b/helpers/promise/createSemaphore.bench.ts new file mode 100644 index 00000000..e8358b34 --- /dev/null +++ b/helpers/promise/createSemaphore.bench.ts @@ -0,0 +1,32 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { createSemaphore } from './createSemaphore'; + +const noop = (): number => 42; + +describe('createSemaphore', () => { + bench('create a semaphore', () => { + createSemaphore(10); + }); + + bench('acquire + release, uncontended', async () => { + const semaphore = createSemaphore(10); + const release = await semaphore.acquire(); + release(); + }); + + bench('run(), uncontended', async () => { + const semaphore = createSemaphore(10); + await semaphore.run(noop); + }); + + bench('run(), 20 concurrent callers over 10 permits', async () => { + const semaphore = createSemaphore(10); + await Promise.all(Array.from({ length: 20 }, () => semaphore.run(noop))); + }); +}); diff --git a/helpers/set/countBy.bench.ts b/helpers/set/countBy.bench.ts new file mode 100644 index 00000000..e157855e --- /dev/null +++ b/helpers/set/countBy.bench.ts @@ -0,0 +1,23 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { countBy } from './countBy'; + +const small = new Set([1, 2, 3, 4, 5]); +const large = new Set(Array.from({ length: 10_000 }, (_, i) => i)); + +describe('countBy', () => { + bench('small set, few groups', () => { + countBy(small, (value) => (value % 2 === 0 ? 'even' : 'odd')); + }); + bench('large set, few groups', () => { + countBy(large, (value) => (value % 2 === 0 ? 'even' : 'odd')); + }); + bench('large set, many groups', () => { + countBy(large, (value) => value % 1000); + }); +}); diff --git a/helpers/set/filter.bench.ts b/helpers/set/filter.bench.ts new file mode 100644 index 00000000..765c8607 --- /dev/null +++ b/helpers/set/filter.bench.ts @@ -0,0 +1,20 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { filter } from './filter'; + +const small = new Set([1, 2, 3, 4, 5]); +const large = new Set(Array.from({ length: 10_000 }, (_, i) => i)); + +describe('filter', () => { + bench('small set', () => { + filter(small, (value) => value % 2 === 0); + }); + bench('large set, half match', () => { + filter(large, (value) => value % 2 === 0); + }); +}); diff --git a/helpers/set/map.bench.ts b/helpers/set/map.bench.ts new file mode 100644 index 00000000..b9d74544 --- /dev/null +++ b/helpers/set/map.bench.ts @@ -0,0 +1,23 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { map } from './map'; + +const small = new Set([1, 2, 3, 4, 5]); +const large = new Set(Array.from({ length: 10_000 }, (_, i) => i)); + +describe('map', () => { + bench('small set', () => { + map(small, (value) => value * 10); + }); + bench('large set, no collisions', () => { + map(large, (value) => value * 10); + }); + bench('large set, with collisions (collapsing)', () => { + map(large, (value) => value % 100); + }); +}); diff --git a/helpers/set/toMapByKey.bench.ts b/helpers/set/toMapByKey.bench.ts new file mode 100644 index 00000000..03880058 --- /dev/null +++ b/helpers/set/toMapByKey.bench.ts @@ -0,0 +1,27 @@ +/** + * This file is part of helpers4. + * Copyright (C) 2025 baxyz + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +import { bench, describe } from 'vitest'; +import { toMapByKey } from './toMapByKey'; + +const small = new Set([ + { id: 'a', n: 1 }, + { id: 'b', n: 2 }, + { id: 'c', n: 3 }, +]); +const large = new Set(Array.from({ length: 10_000 }, (_, i) => ({ id: i, n: i * 2 }))); + +describe('toMapByKey', () => { + bench('small set', () => { + toMapByKey(small, (item) => item.id); + }); + bench('large set', () => { + toMapByKey(large, (item) => item.id); + }); + bench('large set, with duplicate keys (collapsing)', () => { + toMapByKey(large, (item) => item.id % 100); + }); +}); diff --git a/package.json b/package.json index e3f6df2e..eb3599b6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@helpers4/helpers4", "version": "3.0.4", - "author": "baxyz ", + "author": "baxyz ", "repository": { "type": "git", "url": "git+https://github.com/helpers4/typescript.git" @@ -76,11 +76,11 @@ "@typescript/native": "npm:typescript@^7.0.2", "@vitest/coverage-v8": "^4.1.10", "fast-check": "^4.9.0", - "fs-extra": "^11.3.6", + "fs-extra": "^11.4.0", "git-cliff": "^2.13.1", "glob": "^13.0.6", - "happy-dom": "^20.11.0", - "oxlint": "^1.74.0", + "happy-dom": "^20.11.1", + "oxlint": "^1.75.0", "rollup": "^4.62.2", "rollup-plugin-dts": "^6.4.1", "rxjs": "^7.0.0", @@ -90,7 +90,7 @@ "vite": "^8.1.5", "vitest": "^4.1.10" }, - "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264", + "packageManager": "pnpm@11.17.0+sha512.cca3cea332ad254bb84145f966d19f4879615210346fc92c79a047f23a0d7b3cca3c3792f0076ba1f1831d277efbcf0a9119b31a9a60eca7fb3d6231f331ef72", "engines": { "node": ">=26.0.0", "browser": "ES2022+" @@ -98,17 +98,5 @@ "runtimes": { "node": ">=26.0.0", "browser": "ES2022+" - }, - "pnpm": { - "overrides": { - "postcss": ">=8.5.10", - "fast-uri": ">=3.1.2", - "brace-expansion": ">=5.0.6", - "qs": ">=6.15.2", - "ws": ">=8.20.1", - "esbuild": ">=0.28.1", - "@babel/core": ">=7.29.6", - "markdown-it": ">=14.2.0" - } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 021e322e..e4b48595 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: overrides: postcss: '>=8.5.10' - fast-uri: '>=3.1.2' - brace-expansion: '>=5.0.6' + fast-uri: '>=3.1.4' + brace-expansion: '>=5.0.8' qs: '>=6.15.2' ws: '>=8.20.1' esbuild: '>=0.28.1' @@ -20,10 +20,10 @@ importers: devDependencies: '@stryker-mutator/core': specifier: ^9.6.1 - version: 9.6.1(@types/node@26.1.1) + version: 9.6.1(@types/node@26.1.1)(supports-color@7.2.0) '@stryker-mutator/vitest-runner': specifier: ^9.6.1 - version: 9.6.1(@stryker-mutator/core@9.6.1(@types/node@26.1.1))(vitest@4.1.10) + version: 9.6.1(@stryker-mutator/core@9.6.1(@types/node@26.1.1)(supports-color@7.2.0))(vitest@4.1.10) '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 @@ -40,8 +40,8 @@ importers: specifier: ^4.9.0 version: 4.9.0 fs-extra: - specifier: ^11.3.6 - version: 11.3.6 + specifier: ^11.4.0 + version: 11.4.0 git-cliff: specifier: ^2.13.1 version: 2.13.1 @@ -49,11 +49,11 @@ importers: specifier: ^13.0.6 version: 13.0.6 happy-dom: - specifier: ^20.11.0 - version: 20.11.0 + specifier: ^20.11.1 + version: 20.11.1 oxlint: - specifier: ^1.74.0 - version: 1.74.0 + specifier: ^1.75.0 + version: 1.75.0 rollup: specifier: ^4.62.2 version: 4.62.2 @@ -77,7 +77,7 @@ importers: version: 8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0) vitest: specifier: ^4.1.10 - version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.0)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)) packages: @@ -608,124 +608,124 @@ packages: '@oxc-project/types@0.139.0': resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} - '@oxlint/binding-android-arm-eabi@1.74.0': - resolution: {integrity: sha512-+gHd12muVI9ZLBaWLPkHt3Fj7jihFjgQ1MGtBaRL8vWrWrI0P7dLUty/cHrHS0oqPYIRgQUJsPu2CExQuMcwNw==} + '@oxlint/binding-android-arm-eabi@1.75.0': + resolution: {integrity: sha512-lutovtFzJqlRaqpZrCqSSGaHZzl9nIxxpjLzhSRLunN6dCLylj0uzlCyQGaQDIys7rrv8kVXiFO+R4Zpn0bX7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.74.0': - resolution: {integrity: sha512-xjKdoMB+H+RCOByv/7l7nfIGW9mlOisqYdcyC75UqYuQecLpReAeEYUf2CNeDEI3KtmUgxpRw/+c63y4AeF/Bw==} + '@oxlint/binding-android-arm64@1.75.0': + resolution: {integrity: sha512-hXI0hDgHkw4w5nfru72aG7y+2iQJmC4waH/KV6H/hbgA6yAP5jYNx0P9yug15Hs0tWl/+mda3Jjn/2gmDT48tw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.74.0': - resolution: {integrity: sha512-iUK7wvc6sejMKsC+Pt67mntoF5weFcyEunhZfLJceU6gL419mexz5wBkSx/EnkFBExMLNtOi9fnDSc5xfK0IzQ==} + '@oxlint/binding-darwin-arm64@1.75.0': + resolution: {integrity: sha512-D91BWbK/dMYfCcrghspPIuKs2D9LF4Z/OabVSQjw1AO6PWxArD7teDA48bm0ySFqWDaPVqmQRl5GMWNglTXyrQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.74.0': - resolution: {integrity: sha512-ggKc/tn5SJ1u2yG2izC6VKODfYKV8MQ2AicJlNzOjuyrC29udvOef6/JzK2r32xqCnBDLFouR1VCkjzEI0/N9Q==} + '@oxlint/binding-darwin-x64@1.75.0': + resolution: {integrity: sha512-02mpwzf12BonZ6PT0TuQoomvEh2kVl2WGBIKWezCyToIS+rYkQZ6GXnARBAl9A4Ovm2V+Xe7M4KretyqmmcnJQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.74.0': - resolution: {integrity: sha512-u++dH/43jy9hTLbneaWlS0gla/Bp1JdwJ2zgevCl8nDFUh6qRCGMxcL0f0lb7By3A9p/LfFr+7cG4HU1hG856g==} + '@oxlint/binding-freebsd-x64@1.75.0': + resolution: {integrity: sha512-qZJgLnDaBsiL5YESx2t/TZ8eXkL9fEkKoXEdzegROhlz9A0lgyGnZ0dAzJrh7LJAHQl2K9RdRueN2s/9N7+odg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.74.0': - resolution: {integrity: sha512-Sj1zmtFDVTPeIbIz4ZfcXAbFHqCmKCXdCUlAJzvTF7I20NTH1RDpoF2PhkqNODutJzVhJYmm3oz0GwgY+tvE2g==} + '@oxlint/binding-linux-arm-gnueabihf@1.75.0': + resolution: {integrity: sha512-7XlaWA5BJD3XpCfrEqjEe6Zseeb14S7QGa304XfwKignRaKQ+eIj775BQ7nIslggWickl4IsPUFqJ+/gAyNHVg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.74.0': - resolution: {integrity: sha512-//PKyQb/tQXcHArx2f7z+oVI/eMS2Jpv+edNuAtOrgIhWdGcpHxogveAxzmF2rpH1AIHp4Hq04RF/rgJdiICnQ==} + '@oxlint/binding-linux-arm-musleabihf@1.75.0': + resolution: {integrity: sha512-av6Tpv8yrcMMMOadOqENBhlsLRcGFXXwoQ0hzHhsmS9FJ4Wioy8we427GbcMe2XTxmL2e60T67H1Dyr3up+tAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.74.0': - resolution: {integrity: sha512-/k1Me+aX2tjuH10K62mLS0y8cLkJBHX6Ce0xPK+eWeel4bSdEGZ8dv4+hYMzg0GrSmjwy4yAYsDPeEeKBft/2w==} + '@oxlint/binding-linux-arm64-gnu@1.75.0': + resolution: {integrity: sha512-WcUhd8fHT5plrA14lANevl+hOl815mVI5t2hU21oFWrZKFXIVV/Sr4rWQV0NzSvzBupbMLNc5ErEA6Ehxh5jMg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.74.0': - resolution: {integrity: sha512-3tFSjBxc5D8/zvjEuLvOqcA8ZXKD0+6NuaVO/edeamNc49MoAsbfaC9s1UiwODwgF6slGaF8yJA2TPkukd77tg==} + '@oxlint/binding-linux-arm64-musl@1.75.0': + resolution: {integrity: sha512-UWzp5wRHFe/ESO3+eEaxXsTkYTGLYjnTsi/I5neEacXSItQ6WNleapfOAeA4x2b8nyhJ4uQxqvtv9pHv8kWJtQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.74.0': - resolution: {integrity: sha512-9QggtPkSPXOCTu8Szis7auOK/sC7KdQaN+/TujP7YVVhzCAOhgdRfgv8uEz0r2tk5xdgus5rLYUrCDoZNtiRUw==} + '@oxlint/binding-linux-ppc64-gnu@1.75.0': + resolution: {integrity: sha512-XEVRwGMLKCUKrvhLAz4F6AIh8MJrQVdSZtAmPpRZt9tGPsUnamPOcl3dS/ZQzJnar/Ymgc//+xho0L60Emzuxg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.74.0': - resolution: {integrity: sha512-VM5VPUJ4DJIWiK+AZn8FScUqMr6OFrCAYybMYjEEi7W13ParI64MByiXTkKMqZpBmvQ9zxl9Ebq2VUOiZRJYUg==} + '@oxlint/binding-linux-riscv64-gnu@1.75.0': + resolution: {integrity: sha512-mAG4DUXqfLC8cTjMD2kt3jDmVzFREYtDyeLNdLdsCcBc4Zbl2EMuiFektGBilQwkNjYnMvCqJs55U+Hyb+b+jw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.74.0': - resolution: {integrity: sha512-SaDY1gh9rOA592J54g+gu5hkOFFQBZsMmIYHs+NRHG+Uq0OxtuuCXMWQ3vu1830Eugv5uMXyjG+bv2Z9y4IXjw==} + '@oxlint/binding-linux-riscv64-musl@1.75.0': + resolution: {integrity: sha512-95hrAvriAlI+pekSomTFIn0+bawMDlDwTNVmdjsFusTHyL2JWh7TWvRNG/Lkim72uN8OiCcO9wcaC6omLP5E3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.74.0': - resolution: {integrity: sha512-ZATQeHZCyr6MbDveg0obD5sxLHFOghtOdC5jwVwYlvFWqtFOxctgFEG6Ef/64hYvZrWyhyCckB10AelqLopeDA==} + '@oxlint/binding-linux-s390x-gnu@1.75.0': + resolution: {integrity: sha512-4b6f2+FrtruAESrCqIKcrarzfrSx+wk2QNcp+RT91/Prc+pMQMAfyZ1rG1c3tFQNl8Bc616tx40uNXyxNBRPbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.74.0': - resolution: {integrity: sha512-+aIvJyrdeD7LwCQ2WYLMUWNmnbeDRSPb40aBYtPjD9+PTqUwgJnk+HK5yLfSMeqXrMrDhE9uTmtt2y50tvjhHw==} + '@oxlint/binding-linux-x64-gnu@1.75.0': + resolution: {integrity: sha512-nshAhrUvXFUWOvqQ2soIw7HFNWvpvEV4o0cYSqPtzLiPF5gKyYTDOOTJ6Rn8g8K/iGvPIrbDA4v8+5MvnjJrrg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.74.0': - resolution: {integrity: sha512-XyktaR8lhK2qWiCK0Tk8oYD+/cgn+oHA6ddRnxSSXUKkkojkV78CmShZUxQF+yrBFs0SuW+JBOPG6hecyc/iZg==} + '@oxlint/binding-linux-x64-musl@1.75.0': + resolution: {integrity: sha512-e4jNxLKnxLC6sYBQRxrI2pgIIxnmMtF8U/VwNYcjTT/CLS+spH624cYVnj07bTKwaEWT37/e025isOs6j/0xqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.74.0': - resolution: {integrity: sha512-mzbjrPl4neaVUiJ1fUiEUxTGaSZBoiKtaoB6jmIpz9S+VOA2vDYmJpihQ82w6178V5jxziclTg8Cgj5yF6tTDg==} + '@oxlint/binding-openharmony-arm64@1.75.0': + resolution: {integrity: sha512-hZ2lH+1qLf/DiEP9UWuQTK2JWj/BgvMB4jhIV4SmNU1wfEiYYX4TynQyAZXx0j9X4qRYizAL042SKaV+8ynh4w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.74.0': - resolution: {integrity: sha512-vUAe9okpS2Oa5+lX67lqHMuNUvfkleRKwrUDJ/WJBsgmddvZ1mrsh2HVmuFDRzqFELhaJhFaCNOuR6a7L3rtIA==} + '@oxlint/binding-win32-arm64-msvc@1.75.0': + resolution: {integrity: sha512-Ilj6PNzGDS3bCU0MSJH7Msh0NhH+T/mRp2shwg+q+GHeVlPwP5LEboW96aW+3kVKFk6zYZy1Xi5pZkqZh6X8KQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.74.0': - resolution: {integrity: sha512-yyXXJyYYSXL4I8K8jAWjJs+J3fa9gH2JmEbo4f5adm+1tNC9itseicBNuwK7BDHvqQ5J534s+yDULu89vYL2ZQ==} + '@oxlint/binding-win32-ia32-msvc@1.75.0': + resolution: {integrity: sha512-QVit2nOEOiPhkmsrksPSkoGCdnZRNkspt8fwoYyP09te1VEbnSj4LAxua4rc8FKTmWkySVe05j8iz9GXYfF1AQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.74.0': - resolution: {integrity: sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA==} + '@oxlint/binding-win32-x64-msvc@1.75.0': + resolution: {integrity: sha512-DSxnNkBUAYARPwJtR12Ig3deWr8w0H997xP6jy33i+e0SyYJw8FKuz4+cZtpmPEhQmvlPJE3X/2vNxDmLkd/rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1235,9 +1235,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.8: + resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + engines: {node: 20 || >=22} browserslist@4.28.2: resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} @@ -1370,8 +1370,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-uri@4.1.1: + resolution: {integrity: sha512-YPOs1zD5TG2+EZt+r88LwF6mclA7TPkpwMP7ZN3TO2HiHS8TXvq7QA/17iJsV9dubcLo/f8eEYqMBruyQV21hQ==} fast-wrap-ansi@0.2.0: resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} @@ -1389,8 +1389,8 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} - fs-extra@11.3.6: - resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} + fs-extra@11.4.0: + resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==} engines: {node: '>=14.14'} fsevents@2.3.3: @@ -1463,8 +1463,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - happy-dom@20.11.0: - resolution: {integrity: sha512-XogN4asPd1a56di9prVG6bZxteNcXsZxxKmAvcEfc5Px5Ca2hMyMgk8wvqK2K1V8zXg40j9VANXsDaJYh9DeNA==} + happy-dom@20.11.1: + resolution: {integrity: sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg==} engines: {node: '>=20.0.0'} has-flag@4.0.0: @@ -1710,12 +1710,12 @@ packages: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} - oxlint@1.74.0: - resolution: {integrity: sha512-odGl2s2x5IOJoj3A0v1k0PGBXVFBZeZ2+AK/+K2MJur7Ghi3bkyX5NuLUWHKqa4js1wjep3hJeuTQJOlr+4+dA==} + oxlint@1.75.0: + resolution: {integrity: sha512-m9WzjRcRYA/uqIZDa9tclrieoPJ/ln1QYTKdFx6NUOs8uY5DiHlIwRQoCrHT6OM6O3ww3l2skY5gO7G7ZphE7g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.24.0' + oxlint-tsgolint: '>=7.0.2001' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -2089,20 +2089,20 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7': + '@babel/core@7.29.7(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2137,15 +2137,15 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.7)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@7.2.0) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@7.2.0) + '@babel/traverse': 7.29.0(supports-color@7.2.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -2154,42 +2154,42 @@ snapshots: '@babel/helper-globals@7.29.7': {} - '@babel/helper-member-expression-to-functions@7.28.5': + '@babel/helper-member-expression-to-functions@7.28.5(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@7.2.0) '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.28.6': + '@babel/helper-module-imports@7.28.6(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@7.2.0) '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.29.7': + '@babel/helper-module-imports@7.29.7(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.28.6 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-imports': 7.28.6(supports-color@7.2.0) '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2199,18 +2199,18 @@ snapshots: '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.7)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@7.2.0) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + '@babel/helper-skip-transparent-expression-wrappers@7.27.1(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@7.2.0) '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -2238,73 +2238,73 @@ snapshots: dependencies: '@babel/types': 7.29.7 - '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.7)': + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0)) transitivePeerDependencies: - supports-color - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.7)': + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.7)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.7)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.7)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.7)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.7)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@7.2.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0)) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.29.7)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0)) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2320,7 +2320,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.0': + '@babel/traverse@7.29.0(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 @@ -2328,11 +2328,11 @@ snapshots: '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/types': 7.29.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.7(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 @@ -2340,7 +2340,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2600,61 +2600,61 @@ snapshots: '@oxc-project/types@0.139.0': {} - '@oxlint/binding-android-arm-eabi@1.74.0': + '@oxlint/binding-android-arm-eabi@1.75.0': optional: true - '@oxlint/binding-android-arm64@1.74.0': + '@oxlint/binding-android-arm64@1.75.0': optional: true - '@oxlint/binding-darwin-arm64@1.74.0': + '@oxlint/binding-darwin-arm64@1.75.0': optional: true - '@oxlint/binding-darwin-x64@1.74.0': + '@oxlint/binding-darwin-x64@1.75.0': optional: true - '@oxlint/binding-freebsd-x64@1.74.0': + '@oxlint/binding-freebsd-x64@1.75.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.74.0': + '@oxlint/binding-linux-arm-gnueabihf@1.75.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.74.0': + '@oxlint/binding-linux-arm-musleabihf@1.75.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.74.0': + '@oxlint/binding-linux-arm64-gnu@1.75.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.74.0': + '@oxlint/binding-linux-arm64-musl@1.75.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.74.0': + '@oxlint/binding-linux-ppc64-gnu@1.75.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.74.0': + '@oxlint/binding-linux-riscv64-gnu@1.75.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.74.0': + '@oxlint/binding-linux-riscv64-musl@1.75.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.74.0': + '@oxlint/binding-linux-s390x-gnu@1.75.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.74.0': + '@oxlint/binding-linux-x64-gnu@1.75.0': optional: true - '@oxlint/binding-linux-x64-musl@1.74.0': + '@oxlint/binding-linux-x64-musl@1.75.0': optional: true - '@oxlint/binding-openharmony-arm64@1.74.0': + '@oxlint/binding-openharmony-arm64@1.75.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.74.0': + '@oxlint/binding-win32-arm64-msvc@1.75.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.74.0': + '@oxlint/binding-win32-ia32-msvc@1.75.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.74.0': + '@oxlint/binding-win32-x64-msvc@1.75.0': optional: true '@rolldown/binding-android-arm64@1.1.5': @@ -2816,11 +2816,11 @@ snapshots: tslib: 2.8.1 typed-inject: 5.0.0 - '@stryker-mutator/core@9.6.1(@types/node@26.1.1)': + '@stryker-mutator/core@9.6.1(@types/node@26.1.1)(supports-color@7.2.0)': dependencies: '@inquirer/prompts': 8.4.1(@types/node@26.1.1) '@stryker-mutator/api': 9.6.1 - '@stryker-mutator/instrumenter': 9.6.1 + '@stryker-mutator/instrumenter': 9.6.1(supports-color@7.2.0) '@stryker-mutator/util': 9.6.1 ajv: 8.18.0 chalk: 5.6.2 @@ -2848,14 +2848,14 @@ snapshots: - '@types/node' - supports-color - '@stryker-mutator/instrumenter@9.6.1': + '@stryker-mutator/instrumenter@9.6.1(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/generator': 7.29.1 '@babel/parser': 7.29.2 - '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.7) - '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.7) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@stryker-mutator/api': 9.6.1 '@stryker-mutator/util': 9.6.1 angular-html-parser: 10.4.0 @@ -2867,14 +2867,14 @@ snapshots: '@stryker-mutator/util@9.6.1': {} - '@stryker-mutator/vitest-runner@9.6.1(@stryker-mutator/core@9.6.1(@types/node@26.1.1))(vitest@4.1.10)': + '@stryker-mutator/vitest-runner@9.6.1(@stryker-mutator/core@9.6.1(@types/node@26.1.1)(supports-color@7.2.0))(vitest@4.1.10)': dependencies: '@stryker-mutator/api': 9.6.1 - '@stryker-mutator/core': 9.6.1(@types/node@26.1.1) + '@stryker-mutator/core': 9.6.1(@types/node@26.1.1)(supports-color@7.2.0) '@stryker-mutator/util': 9.6.1 semver: 7.7.4 tslib: 2.8.1 - vitest: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.0)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)) '@tybys/wasm-util@0.10.3': dependencies: @@ -2991,7 +2991,7 @@ snapshots: obug: 2.1.3 std-env: 4.2.0 tinyrainbow: 3.1.0 - vitest: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.0)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)) '@vitest/expect@4.1.10': dependencies: @@ -3037,7 +3037,7 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 + fast-uri: 4.1.1 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -3057,7 +3057,7 @@ snapshots: baseline-browser-mapping@2.10.18: {} - brace-expansion@5.0.7: + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.4 @@ -3103,9 +3103,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 des.js@1.1.0: dependencies: @@ -3204,7 +3206,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.2: {} + fast-uri@4.1.1: {} fast-wrap-ansi@0.2.0: dependencies: @@ -3218,7 +3220,7 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 - fs-extra@11.3.6: + fs-extra@11.4.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 @@ -3293,7 +3295,7 @@ snapshots: graceful-fs@4.2.11: {} - happy-dom@20.11.0: + happy-dom@20.11.1: dependencies: '@types/node': 26.1.1 '@types/whatwg-mimetype': 3.0.2 @@ -3459,7 +3461,7 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.8 minipass@7.1.3: {} @@ -3492,27 +3494,27 @@ snapshots: obug@2.1.3: {} - oxlint@1.74.0: + oxlint@1.75.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.74.0 - '@oxlint/binding-android-arm64': 1.74.0 - '@oxlint/binding-darwin-arm64': 1.74.0 - '@oxlint/binding-darwin-x64': 1.74.0 - '@oxlint/binding-freebsd-x64': 1.74.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.74.0 - '@oxlint/binding-linux-arm-musleabihf': 1.74.0 - '@oxlint/binding-linux-arm64-gnu': 1.74.0 - '@oxlint/binding-linux-arm64-musl': 1.74.0 - '@oxlint/binding-linux-ppc64-gnu': 1.74.0 - '@oxlint/binding-linux-riscv64-gnu': 1.74.0 - '@oxlint/binding-linux-riscv64-musl': 1.74.0 - '@oxlint/binding-linux-s390x-gnu': 1.74.0 - '@oxlint/binding-linux-x64-gnu': 1.74.0 - '@oxlint/binding-linux-x64-musl': 1.74.0 - '@oxlint/binding-openharmony-arm64': 1.74.0 - '@oxlint/binding-win32-arm64-msvc': 1.74.0 - '@oxlint/binding-win32-ia32-msvc': 1.74.0 - '@oxlint/binding-win32-x64-msvc': 1.74.0 + '@oxlint/binding-android-arm-eabi': 1.75.0 + '@oxlint/binding-android-arm64': 1.75.0 + '@oxlint/binding-darwin-arm64': 1.75.0 + '@oxlint/binding-darwin-x64': 1.75.0 + '@oxlint/binding-freebsd-x64': 1.75.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.75.0 + '@oxlint/binding-linux-arm-musleabihf': 1.75.0 + '@oxlint/binding-linux-arm64-gnu': 1.75.0 + '@oxlint/binding-linux-arm64-musl': 1.75.0 + '@oxlint/binding-linux-ppc64-gnu': 1.75.0 + '@oxlint/binding-linux-riscv64-gnu': 1.75.0 + '@oxlint/binding-linux-riscv64-musl': 1.75.0 + '@oxlint/binding-linux-s390x-gnu': 1.75.0 + '@oxlint/binding-linux-x64-gnu': 1.75.0 + '@oxlint/binding-linux-x64-musl': 1.75.0 + '@oxlint/binding-openharmony-arm64': 1.75.0 + '@oxlint/binding-win32-arm64-msvc': 1.75.0 + '@oxlint/binding-win32-ia32-msvc': 1.75.0 + '@oxlint/binding-win32-x64-msvc': 1.75.0 parse-ms@4.0.0: {} @@ -3777,7 +3779,7 @@ snapshots: tsx: 4.23.1 yaml: 2.9.0 - vitest@4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.0)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)): + vitest@4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(tsx@4.23.1)(yaml@2.9.0)) @@ -3802,7 +3804,7 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) - happy-dom: 20.11.0 + happy-dom: 20.11.1 transitivePeerDependencies: - msw diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..ed0aeb0f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,11 @@ +allowBuilds: + esbuild: true +overrides: + postcss: '>=8.5.10' + fast-uri: '>=3.1.4' + brace-expansion: '>=5.0.8' + qs: '>=6.15.2' + ws: '>=8.20.1' + esbuild: '>=0.28.1' + '@babel/core': '>=7.29.6' + markdown-it: '>=14.2.0'