diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..d88011f --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ee51031 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5f21759 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install deps + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm typecheck + + - name: Test + run: pnpm test -- --runInBand + + - name: Build + run: pnpm build diff --git a/.gitignore b/.gitignore index bff41c0..75cdbe8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,107 @@ -.DS_STORE -node_modules -.flowconfig -*~ -*.pyc -.grunt -_SpecRunner.html -__benchmarks__ +# ------------------------------------------------------- +# 🧠 CORE: Node / TypeScript / Reflex Monorepo +# ------------------------------------------------------- + +# Dependencies +node_modules/ +.pnpm-store/ +.pnp.* +.pnp.cjs +.pnp.loader.mjs + +# Build outputs +dist/ build/ -remote-repo/ +lib/ +out/ +tsbuildinfo +*.tsbuildinfo + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +*.log + +# Coverage and test output coverage/ -*.log* +.nyc_output/ +__tests__/temp/ +__benchmarks__/ +*.lcov +jest-cache/ +jest-test-results.json + +# ------------------------------------------------------- +# 🧩 IDE & System +# ------------------------------------------------------- +.idea/ +.vscode/ +*.iml *.sublime-project *.sublime-workspace -.idea -*.iml -.vscode *.swp *.swo -drafts/ \ No newline at end of file +*.tmp +*~ +.DS_Store +Thumbs.db +desktop.ini + +# ------------------------------------------------------- +# 🧰 Tools / Build Systems +# ------------------------------------------------------- +.grunt/ +gulpfile.js +webpack.config.js +rollup.config.js +parcel-cache/ +.next/ +.nuxt/ +.cache/ +.eslintcache + +# ------------------------------------------------------- +# 🧪 Misc & drafts +# ------------------------------------------------------- +drafts/ +remote-repo/ +*.pyc +.flowconfig +_SpecRunner.html +*.tgz + +# ------------------------------------------------------- +# ⚙️ Environment +# ------------------------------------------------------- +.env +.env.* +!.env.example +*.local +*.secret + +# ------------------------------------------------------- +# 🧱 Monorepo specific +# ------------------------------------------------------- +/packages/*/dist/ +/packages/*/.tsbuildinfo +/packages/*/.turbo/ +/packages/*/.cache/ +/packages/*/node_modules/ + +# ------------------------------------------------------- +# 🚀 Editor backups +# ------------------------------------------------------- +*.bak +*.orig +*~ +*.rej + +# ------------------------------------------------------- +# 🪄 Custom project entries +# ------------------------------------------------------- +drafts/ +logs/ +temp/ +tmp/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..98475b5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm test diff --git a/packages/reflex/.npmignore b/.npmignore similarity index 100% rename from packages/reflex/.npmignore rename to .npmignore diff --git a/.nvmrc b/.nvmrc index 3bf34c2..1eec628 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.19.0 \ No newline at end of file +v25.2.0 \ No newline at end of file diff --git a/packages/reflex-dom/.prettierignore b/.prettierignore similarity index 100% rename from packages/reflex-dom/.prettierignore rename to .prettierignore diff --git a/packages/reflex-dom/AUTHORS b/AUTHORS similarity index 100% rename from packages/reflex-dom/AUTHORS rename to AUTHORS diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9d630a6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,177 @@ +# Code of Conduct — Reflex Community + +![Community First](https://img.shields.io/badge/ethos-community%20first-blue) +![No Toxicity](https://img.shields.io/badge/zero-toxicity-success) +![Action Driven](https://img.shields.io/badge/practice-over%20theory-orange) +![Human Centered](https://img.shields.io/badge/human-centered-purple) + +This is not a “rules list”. +This is a **living operating system for how we treat people** inside the Reflex ecosystem. + +Reflex is not just a technical project. +It is a human environment built for growth, safety, clarity, and respect. + +Participation in this community means accepting and living by the following principles. + +--- + +## Our Core Commitment + +We are committed to providing a harassment-free, supportive, and empowering experience for everyone, regardless of: + +- experience level +- gender identity +- sexual orientation +- disability +- nationality +- communication style +- beliefs or background + +**People come before code. Always.** + +--- + +## The Three Core Dangers (And Our Antidotes) + +### 1. Reality Gap + +When values stay in documents instead of real actions. + +**Counter-measure:** + +- Every principle must be visible in real behavior. +- Every month → at least one public act reflecting this Code. +- If a rule is not practiced → rewrite or remove it. + +--- + +### 2. Moral High Ground Without People + +A code without people becomes a monument to ego. + +**Counter-measure:** + +- No inner circles or “chosen ones” +- Invite at least one new person monthly +- Always ask: + _“Who else could be strengthened by this?”_ + +--- + +### 3. Over-Seriousness + +Coldness destroys creativity and safety. + +**Counter-measure:** + +- Humor is welcome. Humiliation is not. +- Weekly space for lightness, creativity, experimentation +- Complex ideas must include example or analogy + +If people feel unsafe — something is broken. + +--- + +## Expected Behavior + +You are expected to: + +- Treat everyone with respect and empathy +- Use inclusive, kind, clear language +- Assume good intent before judging +- Accept constructive feedback +- Support others’ growth +- Take responsibility for your impact +- Admit mistakes openly + +--- + +## Unacceptable Behavior + +This is not tolerated: + +- Harassment or discrimination +- Personal attacks, sarcasm, humiliation +- Arrogance and superiority +- Threats, intimidation, gaslighting +- Trolling or deliberate disruption + +Being technically right is **never** an excuse to harm someone. + +--- + +## Criticism Rule + +Criticism is only allowed when: + +1. It is constructive +2. It includes a solution +3. It is delivered respectfully + +No solution → no criticism. + +--- + +## Mistakes Are Not Crimes + +- Your mistake → admit it +- Others’ mistakes → help, don’t shame + +Only: + +> “Here is where it happened. Let’s fix it.” + +--- + +## Responsibility of the Stronger + +If you have more experience, visibility, or power: + +- You protect, not dominate +- You create safety, not fear +- You lift, not push down + +Power = responsibility. + +--- + +## Rule for Maintainers + +Maintainers must be: + +- The calmest +- The most respectful +- The most accountable + +Culture always reflects leadership. + +--- + +## Reporting & Enforcement + +Report violations to: **[ADD CONTACT HERE]** + +All reports will be treated confidentially and seriously. + +Possible actions: + +- Warning +- Temporary restriction +- Permanent ban + +Goal: **healing and safety**, not punishment. + +--- + +## The Reflex Test + +Before pressing “Send”, ask yourself: + +> Will this make the person stronger — or smaller? + +If “smaller” — don’t send it. + +--- + +**Reflex is not just a runtime. +It is a space where people grow.** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a33a875 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,136 @@ +# Contributing to Reflex + +First of all — thank you for being here. + +Reflex is not just code. +It is a living system of ideas, people, responsibility, and respect. + +By contributing, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md). + +--- + +## What We Care About Most + +1. People > Code +2. Clarity > Cleverness +3. Useful > Fancy +4. Tested > Believed +5. Documented > Assumed + +--- + +## Ways To Contribute + +You can contribute by: + +- Writing code +- Improving documentation +- Adding tests or benchmarks +- Reporting issues (clearly and respectfully) +- Reviewing PRs +- Proposing ideas (with reasoning) + +Quality > Quantity + +--- + +## Before Opening a PR + +Ask yourself: + +1. Is this needed? +2. Is it clear? +3. Is it tested? +4. Is it documented (even briefly)? +5. Would I feel safe receiving this PR? + +If any answer is “no” — improve before submitting. + +--- + +## How to Communicate + +We prefer: + +- Clear, respectful language +- Structured thoughts +- Examples over abstraction +- Humility over ego +- Questions over assumptions + +Avoid: + +- “This is stupid” +- “You should know this” +- “Obviously…” +- Aggressive tone + +Instead use: + +- “I believe…” +- “Maybe we could…” +- “What if…” +- “Let’s check…” + +--- + +## Commit Style (Simple) + +Use plain, descriptive messages: + +- `fix: prevent double scheduling in scheduler` +- `docs: clarify ownership model` +- `perf: reduce allocations in signal graph` +- `test: add cases for async ordering` + +No poetry. No drama. Just facts. + +--- + +## Code Style Principles + +We care about: + +- Readability +- Predictability +- Minimal magic +- Clear names +- Stable behavior +- Measurable performance + +If something is clever but unreadable — we do not accept it. + +--- + +## Design Contributions + +If you propose architectural or theoretical changes: + +Include: + +1. Problem statement +2. Proposed solution +3. Trade-offs +4. Alternatives considered +5. Example use-case + +This is required. No exceptions. + +--- + +## Important: You Are Not Alone + +If you feel unsure about anything: + +Open a discussion. +Ask a question. +Request clarification. + +There is no shame in asking. + +There is only shame in ego. + +--- + +**A Reflex contributor is not someone who writes code. +A Reflex contributor is someone who makes the space stronger.** diff --git a/LICENSE b/LICENSE index 8a83559..c8fe909 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Andrii Volynets +Copyright (c) 2025-present Andrii Volynets Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Readme.md b/Readme.md index 61afc89..fe8bfd5 100644 --- a/Readme.md +++ b/Readme.md @@ -1,139 +1,241 @@ -# Reflex +
-**Universal Reactive Runtime** +

+ Reflex Logo +

-> **“Reactivity beyond the DOM — one core, any surface”** +

Reflex

+

Universal Reactive Runtime

+

“One contract. One core. Any surface.”

--- -## 🚀 Overview +## What Reflex Actually Is -Reflex is not just another UI framework. -It is a **general-purpose reactive runtime**: a lightweight ownership system, fine-grained signals, and a scheduler — independent of JSX or the DOM. +**Reflex is not a UI framework.** -Unlike React, Solid, or Vue, Reflex is not locked to the browser. You can render into **DOM, Canvas, WebGL, mobile bridges, or custom targets** — the runtime stays the same. UI is just one of many possible frontends. +Reflex is a **deterministic reactive computation engine** with ownership semantics, epoch-based time, and intrusive graph topology. -**Core idea:** One **Owner** per scope governs signals, effects, and components. Lifecycle, dependency tracking, and cleanup all go through it — no leaks, no zombie state. +It can drive: + +- UI frameworks (DOM / Canvas / WebGL / Native) +- Simulation engines +- Reactive servers +- Dataflow pipelines +- Distributed systems +- Game engines +- Orchestration layers + +UI is just one possible **surface adapter** — not the core identity. --- -## ✨ Key Advantages +## Architecture + +### `@reflex/contract` + +Pure mathematical definitions. No logic. No runtime. + +Defines the **invariants** of the system: + +- `NodeKind`, `LifeState`, `Epoch`, `OwnerId` +- `INode`, `ITemporalNode`, `IOwner` +- `IScheduler`, `IAllocator` +- Reactive graph contracts + +This layer is **frozen by design**. It defines what reality means in Reflex. -- **Ownership as the Unit of Life** - Every signal, effect, or component belongs to an owner. Dispose of a scope → everything inside cleans up automatically. +### `@reflex/core` -- **Contextual Dependency Injection** - Context flows naturally down the ownership tree via prototype inheritance. No prop drilling, no manual context management. +The actual engine: -- **Fine-Grained Signals** - Reactive primitives (`signal`, `derived`, `effect`) update only what actually changes. No re-rendering unnecessary nodes. +- Ownership model (Owner Tree) +- Reactive DAG (signals → memos → effects) +- Epoch system (deterministic local time) +- Intrusive graph links (no adjacency arrays) +- Allocation strategies / pooling +- Dirty propagation +- Disposal algorithms +- Context prototype chain +- Event validation (epoch + version + life state) -- **Coarse Transactions & Batching** - Batched updates, snapshots, and async-safe consistency for SSR, hydration, and streaming pipelines. +**No DOM. No JSX. No rendering. No browser assumptions.** -- **Universal Surfaces** - DOM, Canvas, WebGL, server pipelines, native UI — the runtime is agnostic. +Pure logic. -- **Scheduler-Orchestrated Side Effects** - Timers, I/O, DOM patches, or workers run through a unified priority-based queue for smooth interactivity. +### `@reflex/runtime` -- **Lightweight & Fast** - Core size ~6 KB. Predictable scaling from micro widgets to massive app trees. +Surface implementations: + +- DOM adapter +- Scheduler bindings +- Async bridges +- Server integration +- Worker / thread bridges +- Experimental modules + +Uses **only contracts + core**. Swappable. Extensible. --- -## 🧩 Architectural Layers +## Core Model -1. **Ownership Layer (Coarse)** +Reflex operates on **4 fundamental invariants**: - - Scopes, parent/child hierarchy, disposals. - - Lifecycle backbone: mount, unmount, cleanup. +1. **Ownership is the unit of life** — nothing exists without an owner +2. **Reactivity is a DAG, not a tree** — real topological ordering +3. **Time is local (Epochs), not global** — deterministic causality +4. **Nothing exists without a context** — no ambient globals -2. **Reactive Layer (Fine)** +When an owner dies → everything dies safely. No zombies. No leaks. No magical GC. + +--- - - Signals, computed values, DAG dependency graph. - - Minimal updates only where needed. +## Ownership Model + +``` +Root Owner +└─ App Owner + ├─ Graph Owner + │ ├─ Signal A + │ ├─ Computation B + │ └─ Effect C + └─ Feature Owner + └─ Async Effect +``` -3. **Orchestration Layer** +Every reactive node (signal, memo, effect, async callback) has an owner. - - Unified scheduler for effects, timers, I/O, and batching. - - Priorities, deadlines, cancellations. +Child owners inherit: -4. **Surface Layer (Optional)** +- Context +- Scheduling +- Lifetime guarantees - - DOM, Canvas, WebGL, mobile, or custom renderers. +`dispose(owner)` guarantees **deterministic cleanup** of the entire subgraph. --- -## 🔍 Ownership Flow +## Reactive Graph — Real DAG -**Owner Tree Example:** +Reflex builds an **intrusive directed acyclic graph**: ``` -App Owner (macro) -├─ Main Owner -│ ├─ Signal A → Memo 1 → Effect 1 -│ └─ Signal B → Memo 2 → Effect 2 -└─ Footer Owner - └─ Effect 3 +signal → memo → memo → effect + │ ↘ + └─────→ effect ``` -_Signals mark DAG nodes dirty, scheduler flushes only affected computations._ -_Dispose is iterative post-order: children first, then parent._ +- Intrusive links (no arrays) +- O(1) relinking / unlinking +- Deterministic execution order +- Lazy evaluation support +- Stable topology under concurrency -**Dirty propagation:** +Signals **do not notify**. They mark versions and propagate dirtiness. The scheduler decides when to execute. +--- + +## Epoch System + +Reflex doesn't rely on JavaScript time. It uses **local epochs**. + +Each node tracks: + +```ts +epoch: number; +version: number; ``` -Signal A.set(99) - ↓ markDirty -Memo1 → dirty=true -Effect1 → scheduled run -Memo2 → unchanged + +When an event arrives: + +```ts +{ target: Node, payload } + +1. Validate: + - LifeState alive? + - Owner exists? + - Local epoch valid? + - Version matches? + - Observers exist? + +2. Only then → apply mutation ``` +This makes Reflex **asynchronous-safe by construction**. No race conditions. No stale updates. + --- -## 🔍 Reflex vs Existing Frameworks +## Scheduler Model + +The scheduler is not a re-render loop. + +It's a **universal task orchestrator**: -| Capability | React / Solid | Reflex | -| -------------- | ------------------------- | ----------------------------------------- | -| **Core Model** | Component-centric | Ownership-centric (scopes as first-class) | -| **Reactivity** | Hooks / signals (UI only) | Signals for any domain, not tied to UI | -| **Lifecycle** | Hooks / cleanup | Hierarchical ownership + dispose batch | -| **Context** | Context API | Prototype inheritance per scope | -| **Rendering** | DOM-bound | DOM, Canvas, WebGL, native, server | -| **Scheduling** | Fiber (UI only) | General-purpose priority-based scheduler | -| **Philosophy** | UI framework | Universal reactive runtime | +- Effects +- Async callbacks +- DOM patches +- Worker communication +- IO operations +- Microtasks / macrotasks + +Designed for: + +- Priority queues +- Frame-based batching +- Deadline-aware scheduling +- Backpressure handling +- Cooperative yielding + +Closer to an **OS microkernel** than React Fiber. --- -## 📦 Getting Started +## Context System -**Install:** +Contexts use **prototype inheritance**, not maps: -```bash -npm install @reflex/core +```ts +ChildOwner.context = Object.create(ParentOwner.context); ``` -**Basic Signal Example:** +Benefits: + +- O(1) lookup +- Zero registration overhead +- No provider boilerplate +- Fully deterministic +- Instant propagation + +Real lexical scoping — not React's simulated version. + +--- + +## Example ```ts -import { signal, derived, effect } from "@reflex/core"; +import { signal, derived, effect, createScope } from "@reflex/core"; -const count = signal(0); -const doubled = derived(() => count.value * 2); +createScope(() => { + const count = signal(0); + const double = derived(() => count.value * 2); -effect(() => { - console.log(`Count=${count.value}, Double=${doubled.value}`); -}); + effect(() => { + console.log(count.value, double.value); + }); -count.value++; // logs instantly + count.value = 5; +}); ``` -**DOM Example (optional surface binding):** +When the scope ends → automatic cleanup. No manual teardown needed. + +--- + +## Optional DOM Surface ```tsx -import { signal, render } from "@reflex/core/dom"; +import { signal, render } from "@reflex/runtime/dom"; function Counter() { const count = signal(0); @@ -144,53 +246,79 @@ function Counter() { render(, document.getElementById("app")); ``` ---- +DOM is **one renderer** among many. Bind Reflex to: -## 🧠 Why Reflex? +- Canvas / WebGL +- Terminal (TTY) +- Audio graph +- Server nodes +- Unreal / Unity +- AI simulations +- WASM / embedded systems + +--- -A **reflex** is an immediate response to a stimulus. -Reflex delivers **instant, precise state propagation**, independent of UI layers, with lifecycle and scheduling baked in. +## Comparison -**Owner mantra:** +| System | Core Identity | +| ---------- | -------------------------------------- | +| React | UI renderer + state manager | +| Solid | UI reactivity wrapper | +| Vue | UI + templating runtime | +| RxJS | Stream / event library | +| **Reflex** | **General-purpose computation engine** | -> _"Owner knows its children, marks dirty, and batch-cleans everything."_ +React thinks in **components**. +Reflex thinks in **causality graphs**. --- -## ⚡ Internal API Highlights +## Why This Matters -| User API | Internal Owner API | Description | -| ------------------- | --------------------------------------- | -------------------------------- | -| `useState(initial)` | `createSignal(initial)` | Fine-grained reactive value | -| `useEffect(fn)` | `createEffect(() => fn(), autoCleanup)` | Auto-tracked, runs on dirty | -| `useMemo(fn)` | `createMemo(fn)` | Computed, lazy, dependency-aware | -| `useContext(MyCtx)` | `owner._context?.MyCtx` | Prototype-inherited context | -| `onMount(fn)` | `_onScopeMount(fn)` | Called after scope creation | -| `onUnmount(fn)` | `_onCleanup(fn)` | Cleanup on dispose | +Reflex solves by construction: -**Lifecycle Flow:** +- Memory leaks +- Async race corruption +- Zombie updates +- Invalid state replay +- Uncontrolled side effects +- Broken teardown in concurrency -``` -createScope(App) - ↓ currentOwner = App - createSignal(A) - ↓ _owner = App - A.set(val) - ↓ DAG runs - dispose App - ↓ iterative batch cleanup -``` +Because it's built on: + +- Ownership semantics +- Epoch validation +- Topological ordering +- Mathematical invariants + +This is **physics and mathematics** applied to computation. + +--- + +## Roadmap + +- ✅ Contract-core separation +- ✅ Intrusive DAG +- ✅ Epoch & validation +- ✅ Ownership GC +- ✅ Scheduler foundation +- 🔄 Runtime adapters +- 🔄 Devtools & visualizer +- 🔄 Persistence layer +- 🔄 Distributed graph support +- 🔄 WASM / Rust kernel --- -## 📚 Resources +## Philosophy -- Documentation (coming soon): [reflex.dev/docs](https://reflex.dev/docs) -- GitHub: [github.com/reflex-ui/core](https://github.com/reflex-ui/core) -- Community: [X](https://x.com/reflex_ui) • Discord +> "Do not re-render reality. +> Change only what actually changes." --- -## 🏁 License +## License + +MIT © 2025 Andrii Volynets -MIT License © 2025 Andrii Volynets +
diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b1d1732 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,59 @@ +# Security Policy — Reflex + +Security matters because people matter. + +If you find a vulnerability or risky behavior in Reflex, please report it responsibly. + +--- + +## Supported Versions + +Only the **latest release** is actively supported for security issues. + +--- + +## Reporting a Vulnerability + +Please report security concerns to: + +**[ADD CONTACT HERE]** + +Include: + +- Description of the issue +- Steps to reproduce +- Potential impact +- Relevant code or links +- Your environment (OS, Node version, etc.) + +**Please do NOT open a public issue for security vulnerabilities.** + +We will: + +- Acknowledge your report within 72 hours +- Investigate it carefully +- Communicate progress when possible +- Credit you (if you want) after resolution + +--- + +## Responsible Disclosure + +We strongly believe in responsible disclosure. + +Do not exploit, publish, or discuss vulnerabilities publicly before we address them. + +Our priority is **protecting people first**, not publicity. + +--- + +## Security Philosophy + +Reflex believes: + +- Security is part of architecture +- Simplicity reduces attack surface +- Transparency builds trust +- Humans are more important than systems + +We value your help. Thank you for protecting this space. diff --git a/assets/reflex-arch.svg b/assets/reflex-arch.svg new file mode 100644 index 0000000..496c50f --- /dev/null +++ b/assets/reflex-arch.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + @reflex/contract + laws & interfaces + + + + @reflex/runtime + time & scheduler + + + + @reflex/core + reactive structure + + + + reflex + public API + + + + reflex-dom + platform adapter + + + + + + + diff --git a/assets/reflex-dragon-gold.png b/assets/reflex-dragon-gold.png new file mode 100644 index 0000000..5ae33d0 Binary files /dev/null and b/assets/reflex-dragon-gold.png differ diff --git a/compiler/.gitignore b/compiler/.gitignore new file mode 100644 index 0000000..58818cc --- /dev/null +++ b/compiler/.gitignore @@ -0,0 +1,21 @@ +# Rust +/target +Cargo.lock + +# Logs and dumps +*.log +*.dmp + +# IDEs +.idea/ +.vscode/ +*.iml + +# OS +.DS_Store +Thumbs.db + +# Coverage / benches +*.profraw +*.profdata +/coverage diff --git a/packages/reflex/LICENSE b/compiler/LICENSE similarity index 99% rename from packages/reflex/LICENSE rename to compiler/LICENSE index 8a83559..923599d 100644 --- a/packages/reflex/LICENSE +++ b/compiler/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/compiler/README.md b/compiler/README.md index e69de29..cf6f9a6 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -0,0 +1,14 @@ +# 🦀 Reflex Compiler + +> **A modern compiler for the Reflex language, built with Rust.** +> +> Designed with modular architecture, fast lexing, and experimental IR. + +--- + +## 🚀 Quick Start + +```bash +git clone https://github.com/username/mylang.git +cd mylang +cargo run -- examples/hello.lang diff --git a/config/eslint.config.mjs b/config/eslint.config.mjs new file mode 100644 index 0000000..ccfb693 --- /dev/null +++ b/config/eslint.config.mjs @@ -0,0 +1,38 @@ +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; + +/** @type {import("eslint").Linter.FlatConfig[]} */ +export default [ + { + ignores: ["dist", "coverage", "node_modules"] + }, + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked.map((cfg) => ({ + ...cfg, + languageOptions: { + ...cfg.languageOptions, + parserOptions: { + ...cfg.languageOptions?.parserOptions, + projectService: { + allowDefaultProject: true + }, + tsconfigRootDir: import.meta.dirname + } + } + })), + { + files: ["**/*.ts", "**/*.tsx"], + languageOptions: { + parserOptions: { + project: "./tsconfig.json" + } + }, + rules: { + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/consistent-type-imports": "warn", + "@typescript-eslint/no-explicit-any": "warn" + } + }, + eslintConfigPrettier +]; diff --git a/config/prettier.config.mjs b/config/prettier.config.mjs new file mode 100644 index 0000000..5fa76b6 --- /dev/null +++ b/config/prettier.config.mjs @@ -0,0 +1,6 @@ +export default { + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "semi": true +} diff --git a/config/vite.config.ts b/config/vite.config.ts new file mode 100644 index 0000000..5c59259 --- /dev/null +++ b/config/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "vite"; +import { resolve } from "node:path"; + +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, "src/index.ts"), + name: "@reflex/core", + fileName: (format) => `my-lib.${format}.js`, + formats: ["es", "cjs"] + }, + rollupOptions: { + external: [ + // "react", "lodash" и пр., если они peerDependencies + ], + output: { + globals: { + // react: "React" + } + } + } + }, +}); diff --git a/config/vitest.config.ts b/config/vitest.config.ts new file mode 100644 index 0000000..2afd42d --- /dev/null +++ b/config/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["tests/**/*.test.{ts,tsx}"], + coverage: { + reporter: ["text", "lcov"], + include: ["src"] + } + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..b08af00 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "reflex-monorepo", + "private": true, + "version": "0.7.0", + "packageManager": "pnpm@9.0.0", + "scripts": { + "build": "pnpm -r build", + "test": "pnpm -r test", + "bench": "pnpm -r bench", + "lint": "pnpm -r lint", + "typecheck": "pnpm -r typecheck", + "release": "changeset version && pnpm install && changeset publish", + "prepare": "husky" + }, + "devDependencies": { + "0x": "^6.0.0", + "@changesets/cli": "^2.27.0", + "@eslint/js": "^9.0.0", + "eslint": "^9.0.0", + "fast-check": "^4.3.0", + "husky": "^9.0.0", + "lint-staged": "^15.0.0", + "prettier": "^3.3.0", + "ts-node": "^10.9.2", + "typescript": "^5.6.0", + "typescript-eslint": "^8.0.0", + "vite": "^6.0.0", + "vitest": "^4.0.0" + } +} diff --git a/packages/@reflex/contract/CONTRACTS.md b/packages/@reflex/contract/CONTRACTS.md new file mode 100644 index 0000000..f85a696 --- /dev/null +++ b/packages/@reflex/contract/CONTRACTS.md @@ -0,0 +1,149 @@ +# Reflex Contracts + +This document describes the **contracts and invariants** defined in `@reflex/contract`. +They specify _what must hold_ in a Reflex runtime, independently of any particular implementation. + +## 1. Time & Scheduling + +### Types + +- `Task = () => void` — a unit of work scheduled by the runtime +- `Epoch = number` — logical time, local to the runtime + +### Interfaces + +- `IScheduler` + - `schedule(task: Task): void` + - Must enqueue the task for execution (immediately or later) + - Must be non-blocking for valid tasks + +- `ITemporalScheduler extends IScheduler` + - `readonly epoch: Epoch` + - `nextEpoch(): void` + - Invariant: `epoch` is monotonically increasing + +## 2. Allocation + +- `IAllocator` + - `create(): T` — returns a fresh instance + - `destroy(node: T): void` — node is considered invalid after this call + +No pooling or GC policy is defined at this level. + +## 3. Graph / Causality + +- `IGraph` + - `link(source: N, target: N): void` + - `unlink(source: N, target: N): void` + - `sources(node: N): Iterable` + - `targets(node: N): Iterable` + +Interpretation: + +- `source → target` means “target depends on source” +- `sources(node)` are upstream dependencies +- `targets(node)` are downstream dependents + +Invariants: + +- `link()` must be idempotent for the same pair +- `sources(node)` and `targets(node)` must not include `node` itself + +## 4. Runtime Container + +- `IRuntime` + - `readonly scheduler: IScheduler | ITemporalScheduler` + - `readonly allocator: IAllocator` + - `readonly graph: IGraph` + +- `IRuntimeCallable` + - `(action: (runtime: IRuntime) => T): T` + +This layer defines **what a minimal execution environment provides**: +scheduling, allocation, and causality graph. + +## 5. Ownership & Lifetime + +### Types + +- `OwnerId = number` +- `LifeState` + - `CREATED → ATTACHED | ACTIVE` + - `ATTACHED → ACTIVE | DISPOSING` + - `ACTIVE → DISPOSING` + - `DISPOSING → DISPOSED` + - `DISPOSED` is terminal + +### Lifetime + +- `ILifetime` + - `createdAt: Epoch` + - `updatedAt: Epoch` + - `disposedAt: Epoch | null` + +Invariants: + +- `createdAt <= updatedAt` +- If `disposedAt != null` then `disposedAt >= updatedAt` +- After final disposal, `updatedAt === disposedAt` + +### Owned / Owner + +- `IOwned` + - `readonly owner: IOwner | null` + - `readonly state: LifeState` + - `attach(owner: IOwner): void` + - `detach(): void` + - `dispose(): void` (idempotent) + +Invariants: + +- A node has at most one owner at a time +- If `owner !== null`, then `owner.children` must contain this node +- `dispose()` must eventually drive `state` to `DISPOSED` + +- `IOwner extends IOwned` + - `readonly id: OwnerId` + - `readonly children: ReadonlySet` + - `adopt(node: IOwned): void` + - `release(node: IOwned): void` + +Ownership invariants: + +- Ownership forms a tree (no cycles) +- After `adopt(node)`: + - `node.owner === this` + - `children` contains `node` +- After `release(node)` when `node.owner === this`: + - `node.owner === null` + - `children` no longer contains `node` + +### Cascading Disposal + +- `ICascading` + - `cascadeDispose(): void` + +- `ICascadingOwner extends IOwner, ICascading` + +Invariants: + +- After `cascadeDispose()`: + - `children` should be empty + - all previously owned nodes must be in `DISPOSING` or `DISPOSED` state +- Calling `dispose()` on an `ICascadingOwner` must eventually cascade to all descendants + +### Temporal Nodes + +- `ITemporalNode extends IOwned, ILifetime` + +Invariants: + +- All lifetime and ownership invariants must hold simultaneously + +--- + +With this contract layer in place: + +- `@reflex/core` implements **how** these contracts are realized (intrusive lists, pools, DAG, etc.). +- `@reflex/runtime` chooses policies (schedulers, epochs, modes). +- Your public `reflex` package re-exports only the safe, high-level API. diff --git a/packages/@reflex/contract/package.json b/packages/@reflex/contract/package.json new file mode 100644 index 0000000..c0914a3 --- /dev/null +++ b/packages/@reflex/contract/package.json @@ -0,0 +1,24 @@ +{ + "name": "@reflex/contract", + "version": "0.1.0", + "description": "Core type contracts for Reflex runtime", + "type": "module", + + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + + "files": ["dist"], + + "scripts": { + "build": "tsc -p tsconfig.build.json", + "watch": "tsc -p tsconfig.build.json --watch", + "clean": "rimraf dist" + } +} diff --git a/packages/@reflex/contract/src/index.ts b/packages/@reflex/contract/src/index.ts new file mode 100644 index 0000000..bd63d5a --- /dev/null +++ b/packages/@reflex/contract/src/index.ts @@ -0,0 +1,175 @@ +/* ============================================================ + * Base types + * ============================================================ */ + +export type Task = () => void; +export type Epoch = number; +export type NodeId = number; +export type OwnerId = number; + +/** Packed uint32 causal state */ +export type CausalState = number; + +/* ============================================================ + * Node kinds (META, not causal) + * ============================================================ */ + +export const enum NodeKind { + OWNER = 1 << 0, + SIGNAL = 1 << 1, + COMPUTATION = 1 << 2, + EFFECT = 1 << 3, +} + +/* ============================================================ + * Lifecycle state (META, not causal) + * ============================================================ */ + +export const enum LifeState { + CREATED = 0, + ATTACHED = 1, + ACTIVE = 2, + DISPOSING = 3, + DISPOSED = 4, +} + +/* ============================================================ + * Scheduler + * ============================================================ */ + +export interface IScheduler { + schedule(task: Task): void; +} + +/** + * Optional time-aware scheduler. + * It does NOT own time — it advances the system. + */ +export interface ITemporalScheduler extends IScheduler { + tick(): void; +} + +/* ============================================================ + * Causal store (NEW CORE) + * ============================================================ */ + +export interface CausalSnapshot { + readonly epoch: number; + readonly version: number; + readonly generation: number; + readonly layout: number; +} + +export type NodeStats = { + sync: number; + async: number; + conflicts: number; + lastJump: number; +}; + +export interface ICausalStore { + /** how many nodes are currently allocated */ + readonly size: number; + + /** allocated capacity */ + readonly capacity: number; + + /* ------------ allocation ------------ */ + + allocate(): NodeId; + free(id: NodeId): void; + + /* ------------ access ------------ */ + + raw(id: NodeId): CausalState; + + read(id: NodeId): CausalSnapshot; + write(id: NodeId, epoch: number, version: number, generation: number): void; + + evolve(id: NodeId, stats: NodeStats): void; +} + +/* ============================================================ + * Allocators + * ============================================================ */ + +/** Allocator for graph objects */ +export interface IAllocator { + create(): N; + destroy(node: N): void; +} + +/** Allocator specifically for CausalStore */ +export interface IStateAllocator { + allocate(): NodeId; + free(id: NodeId): void; +} + +/* ============================================================ + * Graph topology (pure structure only) + * ============================================================ */ + +export interface IGraph { + link(source: N, target: N): void; + unlink(source: N, target: N): void; + + sources(node: N): Iterable; + targets(node: N): Iterable; +} + +/* ============================================================ + * Ownership model + * ============================================================ */ + +export interface IOwned { + readonly id: NodeId; // linked to ICausalStore + readonly owner: IOwner | null; + readonly state: LifeState; + + attach(owner: IOwner): void; + detach(): void; + dispose(): void; +} + +export interface IOwner extends IOwned { + readonly id: OwnerId; + children(): Iterable; + + adopt(node: IOwned): void; + release(node: IOwned): void; +} + +export interface ICascading { + cascadeDispose(): void; +} + +export interface ICascadingOwner extends IOwner, ICascading {} + +/* ============================================================ + * Temporal view (NO OWN TIME, ONLY PROXY) + * ============================================================ */ + +export interface ITemporalNode extends IOwned { + readonly id: NodeId; +} + +export interface ITemporalView { + readonly epoch: Epoch; + readonly version: number; + readonly generation: number; +} + +/* ============================================================ + * Runtime (where everything meets) + * ============================================================ */ + +export interface IRuntime { + readonly scheduler: IScheduler; + readonly allocator: IAllocator; + readonly topology: IGraph; + readonly causal: ICausalStore; +} + +export interface IRuntimeCallable { + (fn: (rt: IRuntime) => T): T; +} diff --git a/packages/@reflex/contract/tsconfig.build.json b/packages/@reflex/contract/tsconfig.build.json new file mode 100644 index 0000000..4a2918d --- /dev/null +++ b/packages/@reflex/contract/tsconfig.build.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "noEmit": false, + "allowImportingTsExtensions": false, + + "outDir": "./dist", + "rootDir": "./src", + + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": false, + + "stripInternal": true + }, + + "include": ["src"] +} diff --git a/packages/@reflex/contract/tsconfig.json b/packages/@reflex/contract/tsconfig.json new file mode 100644 index 0000000..832e16a --- /dev/null +++ b/packages/@reflex/contract/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + + "moduleResolution": "bundler", + "strict": true, + "isolatedModules": true, + "skipLibCheck": true, + + "allowImportingTsExtensions": true, + + "noEmit": true + }, + "include": ["src"] +} diff --git a/packages/reflex/.gitignore b/packages/@reflex/core/.gitignore similarity index 100% rename from packages/reflex/.gitignore rename to packages/@reflex/core/.gitignore diff --git a/packages/@reflex/core/package.json b/packages/@reflex/core/package.json new file mode 100644 index 0000000..c0525e3 --- /dev/null +++ b/packages/@reflex/core/package.json @@ -0,0 +1,53 @@ +{ + "name": "@reflex/core", + "version": "0.1.0", + "type": "module", + "description": "Core reactive primitives", + "main": "./dist/index.js", + "module": "dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, + "license": "MIT", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "scripts": { + "dev": "vite", + "build": "tsc --build", + "test": "vitest run", + "bench": "vitest bench", + "bench:flame": "0x -- node dist/tests/ownership.run.js", + "test:watch": "vitest", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "typecheck": "tsc --noEmit", + "prepublishOnly": "pnpm lint && pnpm test && pnpm typecheck && pnpm build", + "release": "changeset version && pnpm install && changeset publish", + "prepare": "husky" + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=20.19.0" + }, + "lint-staged": { + "*.{ts,tsx,js,jsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md,yml,yaml}": [ + "prettier --write" + ] + }, + "devDependencies": { + "@reflex/contract": "workspace:*", + "@types/node": "^24.10.1" + } +} diff --git a/packages/@reflex/core/src/README.md b/packages/@reflex/core/src/README.md new file mode 100644 index 0000000..c02ff84 --- /dev/null +++ b/packages/@reflex/core/src/README.md @@ -0,0 +1,3 @@ +Core — the foundation of Reflex. +Contains pure mechanisms such as ownership, object models, data structures, and time control. +Independent of runtime or adapters. No external side effects. \ No newline at end of file diff --git a/packages/@reflex/core/src/collections/README.md b/packages/@reflex/core/src/collections/README.md new file mode 100644 index 0000000..2251d29 --- /dev/null +++ b/packages/@reflex/core/src/collections/README.md @@ -0,0 +1,3 @@ +Collections — optimized data structures for the Reflex runtime. +Implements queues, stacks, and graphs with predictable memory usage. +Built for minimal allocations and high cache locality. \ No newline at end of file diff --git a/packages/@reflex/core/src/collections/unrolled-queue.ts b/packages/@reflex/core/src/collections/unrolled-queue.ts new file mode 100644 index 0000000..681def5 --- /dev/null +++ b/packages/@reflex/core/src/collections/unrolled-queue.ts @@ -0,0 +1,324 @@ +/** + * @file unrolled-queue.ts + * High-performance Unrolled Queue - Optimized Version + * + * Was inspired by: https://github.com/nodejs/node/blob/86bfdb552863f09d36cba7f1145134346eb2e640/lib/internal/fixed_queue.js + * + * Conceptually similar to Node.js internal `FixedQueue`, + * but generalized into an **unrolled linked queue**. + * Each node is a fixed-size circular buffer, and nodes + * form a singly-linked list — this allows dynamic growth + * with O(1) amortized enqueue/dequeue cost. + * + * Differences from Node's FixedQueue: + * - Multiple circular nodes instead of a single fixed one. + * - Node pooling to minimize GC churn. + * - Fully iterable and clearable. + * + * Visualized: + * + * ┌─────────────────────────────────────────────────────────────┐ + * │ UnrolledQueue │ + * └─────────────────────────────────────────────────────────────┘ + * head tail + * ↓ ↓ + * +-------------+ +-------------+ +-------------+ + * | next ---> | → | next ---> | → | next:null | + * |-------------| |-------------| |-------------| + * | buffer[ ] | | buffer[ ] | | buffer[ ] | + * | circular | | circular | | circular | + * | segment | | segment | | segment | + * +-------------+ +-------------+ +-------------+ + * ▲ ▲ ▲ + * writeIndex writeIndex writeIndex + * readIndex readIndex readIndex + * + * Each node is a circular buffer (power of two in size). + * When one fills, a new node is linked via `.next`. + * When a node is emptied and `.next` exists, it is + * detached and recycled into a node pool. + * + * So enqueue/dequeue always stay O(1), but the structure + * can grow and shrink adaptively with almost no GC pressure. + * + * ────────────────────────────────────────────────────────────── + * Node lifecycle: + * alloc() → use → free() → returned to pool (up to 128 nodes) + * ────────────────────────────────────────────────────────────── + * + * Comparison with Node.js internal `FixedQueue`: + * ┌───────────────────────────────────────────────────────────────────────┐ + * │ Feature │Node.js FixedQueue │ UnrolledQueue (this impl) │ + * ├───────────────────────┼───────────────────┼───────────────────────────┤ + * │ Storage model │ One fixed ring │ Linked list of rings │ + * │ Growth strategy │ None (fixed) │ Dynamic unrolling │ + * │ GC profile │ Stable, static │ Stable, pooled │ + * │ Iterable │ No │ Yes │ + * │ Clear/reset │ Manual reinit │ O(n) node recycle │ + * │ Typical use │ Internal queues │ General-purpose runtime │ + * └───────────────────────┴───────────────────┴───────────────────────────┘ + * + * Performance: + * - O(1) amortized enqueue/dequeue + * - ~4–5 ns per op on V8 12+ + * - Stable memory footprint (≈5–20 MB depending on pool) + * + * Optimizations applied: + * - Removed Nullable type alias (direct T | null usage) + * - Cached head.next lookup in enqueue + * - Pre-computed isFull condition inline + * - Eliminated redundant isEmpty checks + * - Optimized drain() with direct buffer access + * - Removed unnecessary null checks in dequeue + * - Simplified node recycling logic + */ + + +export interface UnrolledQueueOptions { + /** Node (segment) size, must be a power of two for bitmask optimization */ + nodeSize: number; +} + +/** + * Interface definition for UnrolledQueue. + */ +export interface IUnrolledQueue extends Iterable { + readonly length: number; +} + +const NODE_POOL_MAX = 128; +/** Default node size most stable for V8 (power of two) */ +const DEFAULT_NODE_SIZE = 2048 as const; + +/** + * Uses "one empty slot" semantics to differentiate + * full vs empty states. Internally uses bitmask indexing: + * `(index + 1) & mask` for wrapping. + */ +class RefNode { + /** Shared pool for recycling detached nodes */ + private static pool: RefNode[] = []; + + readonly size: number; + readonly mask: number; + + buffer: Array; + readIndex = 0; + writeIndex = 0; + next: RefNode | null = null; + + constructor(size: number) { + this.size = size; + this.mask = size - 1; + this.buffer = new Array(size); + this.readIndex = 0; + this.writeIndex = 0; + this.next = null; + + for (let i = 0; i < size; i++) this.buffer[i] = null; + } + + /** Number of elements currently held */ + get length(): number { + return (this.writeIndex - this.readIndex + this.size) & this.mask; + } + + /** Acquire node from pool or create new one */ + static alloc(size: number): RefNode { + const pool = this.pool as RefNode[]; + const node = pool.pop(); + + if (node) { + node.readIndex = 0; + node.writeIndex = 0; + node.next = null; + return node; + } + + return new RefNode(size); + } + + /** Return node to pool, resetting state (max 128 kept) */ + static free(node: RefNode): void { + if (this.pool.length < NODE_POOL_MAX) { + const b = node.buffer; + const len = b.length; + for (let i = 0; i < len; i++) b[i] = null; + node.readIndex = 0; + node.writeIndex = 0; + node.next = null; + this.pool.push(node); + } + } + + /** @__INLINE__ Push item into buffer (returns false if full) */ + enqueue(item: T): boolean { + // Inline isFull check + const nextWrite = (this.writeIndex + 1) & this.mask; + if (nextWrite === this.readIndex) { + return false; + } + + this.buffer[this.writeIndex] = item; + this.writeIndex = nextWrite; + + return true; + } + + /** @__INLINE__ Pop item from buffer (returns null if empty) */ + dequeue(): T | null { + if (this.readIndex === this.writeIndex) { + return null; + } + + const item = this.buffer[this.readIndex] as T; + this.buffer[this.readIndex] = null; + this.readIndex = (this.readIndex + 1) & this.mask; + + return item; + } + + peek(): T | null { + if (this.readIndex === this.writeIndex) return null; + return this.buffer[this.readIndex] as T; + } +} + +/** + * Enqueue always writes to the current head node. + * If full, allocates a new one and links it. + * + * Dequeue always reads from the current tail node. + * If empty and next exists, the old node is freed + * back into the pool. + * + * Thus, the queue "unrolls" and "collapses" dynamically + * with constant-time operations and minimal GC. + */ +export class UnrolledQueue implements Queueable, IUnrolledQueue { + #nodeSize: number; + #head: RefNode; + #tail: RefNode; + #length: number = 0; + + constructor(options: UnrolledQueueOptions = { nodeSize: DEFAULT_NODE_SIZE }) { + const size = options.nodeSize; + const node = RefNode.alloc(size); + this.#nodeSize = size; + this.#head = node; + this.#tail = node; + this.#length = 0; + } + + get length(): number { + return this.#length; + } + + /** @__INLINE__ Add item to queue head */ + enqueue(item: T): void { + const head = this.#head; + + if (!head.enqueue(item)) { + const newNode = RefNode.alloc(this.#nodeSize); + head.next = newNode; + this.#head = newNode; + newNode.enqueue(item); + } + + this.#length++; + } + + /** @__INLINE__ Remove item from queue tail */ + dequeue(): T | undefined { + if (this.#length === 0) return undefined; + + const tail = this.#tail; + const item = tail.dequeue(); + + if (item === null) return undefined; + + this.#length--; + + const next = tail.next; + if (tail.readIndex === tail.writeIndex && next) { + this.#tail = next; + RefNode.free(tail); + } + + return item; + } + + /** Clear queue and recycle all nodes */ + clear(): void { + let node: RefNode | null = this.#tail; + + while (node) { + const next: RefNode | null = node.next; + RefNode.free(node); + node = next; + } + + const fresh = RefNode.alloc(this.#nodeSize); + this.#head = this.#tail = fresh; + this.#length = 0; + } + + drain(callback: (v: T) => void): number { + let count = 0; + let node = this.#tail; + + while (this.#length !== 0 && node) { + const buf = node.buffer; + const mask = node.mask; + let idx = node.readIndex; + const nodeLen = node.length; + + for (let i = 0; i < nodeLen; i++) { + const val = buf[idx] as T; + buf[idx] = null; + callback(val); + count++; + idx = (idx + 1) & mask; + } + + node.readIndex = idx; + this.#length -= nodeLen; + + const next = node.next; + if (next) { + RefNode.free(node); + this.#tail = next; + node = next; + } else { + break; + } + } + + return count; + } + + /** access current tail element without dequeuing */ + peek(): T | null { + if (this.#length === 0) return null; + return this.#tail.peek(); + } + + estimateNodes(): number { + return 1 + ((this.#length / (this.#nodeSize - 1)) | 0); + } + + /** Iterator: yields items from tail → head */ + *[Symbol.iterator](): Iterator { + for (let n: RefNode | null = this.#tail; n; n = n.next) { + const buf = n.buffer; + const mask = n.mask; + const nodeLen = n.length; + let j = n.readIndex; + + for (let i = 0; i < nodeLen; i++) { + yield buf[j] as T; + j = (j + 1) & mask; + } + } + } +} diff --git a/packages/@reflex/core/src/graph/Readme.md b/packages/@reflex/core/src/graph/Readme.md new file mode 100644 index 0000000..c101e87 --- /dev/null +++ b/packages/@reflex/core/src/graph/Readme.md @@ -0,0 +1,99 @@ +# **Reflex Reactive Graph** + +## **1. Overview** + +Reflex описує реактивність як **орієнтований ациклічний граф обчислень (DAG)**. +Кожен вузол виконує конкретну роль у поширенні змін: + +* **Source** генерує значення та повідомляє залежних. +* **Observer** виконує обчислення та оновлюється, коли змінюється будь-яке з його джерел. + +Модель зберігає причинність, детермінованість та дозволяє чітко контролювати життєвий цикл кожного вузла. + +--- + +## **2. Graph Structure** + +Нехай `G = (V, E)` — реактивний граф Reflex. + +* `V` — множина вузлів (source або observer). +* `E` — множина напрямлених ребер `v → u`, що означає: + `u` залежить від `v`. + +Кожен вузол містить базові поля: + +* **_flags** — бітові стани (dirty, scheduled, running, disposed). +* **_epoch** — локальний причинний час, монотонно зростає при змінах. +* **_version / _uversion** — відбитки локального та upstream-стану для інкрементального оновлення. +* **_sources / _observers** — інтрузивні списки залежностей. + +Ця структура дозволяє реалізувати швидке поширення оновлень та ефективне відстеження залежностей без зайвої алокації. + +--- + +## **3. Core Invariants** + +Щоб граф залишався коректним та детермінованим, Reflex підтримує такі інваріанти: + +1. **Вузол не може залежати від самого себе.** + Будь-яке обчислення формує DAG без циклів. + +2. **Оновлення завжди рухаються вперед по епосі.** + Подія з джерела застосовується лише якщо її `_epoch` не менша ніж у залежного вузла. + Це усуває можливість застарілих оновлень. + +3. **Видалення вузла знімає всі вихідні ребра.** + Всі дочірні та залежні вузли перестають посилатись на нього, + а залишки контексту й cleanup-функцій знищуються. + +4. **Будь-яка зміна залежностей зберігає топологію DAG.** + Вставка або заміна upstream-вузлів відбувається під час фази трекінгу, + гарантується що нове дерево залежностей залишається ациклічним і локально впорядкованим. + +--- + +## **4. Update Flow** + +Оновлення у Reflex проходить через три етапи: + +1. **Mark Dirty** + Вузол відмічається як змінений (`DIRTY`). + Він додається до планувальника, якщо ще не в черзі. + +2. **Schedule & Propagate** + Планувальник перебирає dirty-вузли у причинному порядку + і перевіряє їх `_version` проти `_uversion`. + Якщо хоч одне upstream-джерело новіше — вузол перераховується. + +3. **Commit & Notify** + Після успішного обчислення: + + * `_version++` + * `_epoch` оновлюється для збереження узгодженості + * всі залежні вузли отримують сповіщення + +--- + +## **5. Disposal Semantics** + +Вузол може бути знищений явно або як частина піддерева: + +* всі `_sources` та `_observers` від’єднуються інтрузивно (O(1) операції), +* викликаються cleanup-функції, +* вузол отримує стан `DISPOSED` й більше не бере участі в оновленнях. + +Disposal гарантує чисту причинність та звільнення ресурсів без витоків. + +--- + +## **6. Summary** + +Reflex Reactive Graph — це **низькорівневе реактивне ядро**, яке: + +* працює на DAG без циклів, +* забезпечує строгий причинний порядок без глобального часу, +* має інтрузивні списки для швидкого оновлення структури, +* використовує локальні епохи та версії для інкрементального оновлення, +* гарантує стабільний, детермінований результат обчислень. + +Це фундамент для побудови узгодженої, передбачуваної й високопродуктивної реактивної моделі в Reflex. \ No newline at end of file diff --git a/packages/@reflex/core/src/graph/graph.contract.ts b/packages/@reflex/core/src/graph/graph.contract.ts new file mode 100644 index 0000000..539e735 --- /dev/null +++ b/packages/@reflex/core/src/graph/graph.contract.ts @@ -0,0 +1,218 @@ +import { NodeIndex, GraphNode, GraphEdge } from "./process/graph.node"; +import { + unlinkAllObserversBulkUnsafeForDisposal, + unlinkAllSourcesChunkedUnsafe, + linkSourceToObserverUnsafe, + unlinkSourceFromObserverUnsafe, + hasObserverUnsafe, + hasSourceUnsafe, + replaceSourceUnsafe, +} from "./process/graph.methods"; + +/** + * IGraph + * = + * + * Low-level contract for managing the *structural* topology of the reactive DAG. + * + * This interface owns exactly one responsibility: + * — define, mutate, and traverse dependency edges between GraphNodes. + * + * IMPORTANT: + * - No scheduler logic is allowed here. + * - No phase/state logic (t/v/g/s) is allowed here. + * - No memory or lifecycle logic (except edge unlinking). + * - No business semantics, no reactivity semantics. + * + * IGraph is strictly a thin abstraction over intrusive adjacency lists in + * GraphNode and GraphEdge. Implementations must remain allocation-free and + * branch-minimal wherever possible. + */ +export interface IGraph { + /** + * Creates a new GraphNode bound to an already allocated NodeIndex + * in the causal layout. + * + * The returned node owns its own adjacency lists but contains no edges yet. + */ + createNode(layoutIndex: NodeIndex): GraphNode; + + /** + * Completely detaches the node from the graph: + * - removes all outgoing edges (node → observers) + * - removes all incoming edges (sources → node) + * + * After this call, the node becomes structurally isolated but remains + * a valid object. Memory reclamation or layout index recycling is *not* + * handled here — this is the responsibility of Runtime/Layout/Ownership. + */ + removeNode(node: GraphNode): void; + + /** + * Creates a directed edge source → observer. + * Implementations must not perform cycle detection or safety checks. + * This operation must be O(1) and allocation-free except for the edge itself. + */ + addObserver(source: GraphNode, observer: GraphNode): void; + + /** + * Removes a directed edge source → observer, if it exists. + * If the edge does not exist, the call must be a no-op. + * Must be O(1) on average due to intrusive structure. + */ + removeObserver(source: GraphNode, observer: GraphNode): void; + + /** + * Iterates all observers of the given node: + * source → (observer1, observer2, ...) + * + * Must not allocate or materialize arrays. Must traverse the intrusive list. + */ + forEachObserver(node: GraphNode, fn: (observer: GraphNode) => void): void; + + /** + * Iterates all sources of the given node: + * (source1, source2, ...) → observer + * + * Must not allocate or materialize arrays. Must traverse the intrusive list. + */ + forEachSource(node: GraphNode, fn: (source: GraphNode) => void): void; + + /** + * Returns true if `observer` appears in the outgoing adjacency list of `source`. + * Runtime complexity: O(k), where k = out-degree of source. + */ + hasObserver(source: GraphNode, observer: GraphNode): boolean; + + /** + * Returns true if `source` appears in the incoming adjacency list of `observer`. + * Runtime complexity: O(k), where k = in-degree of observer. + */ + hasSource(source: GraphNode, observer: GraphNode): boolean; + + /** + * Atomically replaces a dependency edge: + * oldSource → observer (removed) + * newSource → observer (added) + * + * This is heavily used by reactive tracking and effect re-binding. + */ + replaceSource( + oldSource: GraphNode, + newSource: GraphNode, + observer: GraphNode, + ): void; +} + +/** + * GraphService (Optimized) + * = + * + * Zero-overhead implementation of IGraph on top of intrusive adjacency lists. + * + * DESIGN GOALS: + * - no internal state: the graph lives entirely inside GraphNode/GraphEdge + * - minimal branching: all hot paths must remain predictable for V8 + * - no defensive checks: the caller is responsible for correctness + * - O(1) edge insertion/removal (amortized) + * - allocation-free traversal + * + * This service is intentionally low-level: it models *pure topology*. + * Higher-level semantics (reactivity, scheduling, cleanup, batching) + * belong to other runtime subsystems. + */ +export class GraphService implements IGraph { + /** + * Creates a new intrusive graph node bound to a specific layout index. + * + * The node starts with: + * - empty incoming adjacency list + * - empty outgoing adjacency list + * - zero-degree in both directions + * + * No edges are implicitly created. + */ + createNode = (layoutIndex: NodeIndex): GraphNode => + new GraphNode(layoutIndex); + + /** + * Destroys all structural connectivity of the given node: + * + * (1) Removes all edges node → observers (outgoing) + * (2) Removes all edges sources → node (incoming) + * + * After removal, the GraphNode becomes an isolated island. + * Memory or layout cleanup must be handled elsewhere. + */ + removeNode = (node: GraphNode): void => ( + unlinkAllObserversBulkUnsafeForDisposal(node), + unlinkAllSourcesChunkedUnsafe(node) + ); + + /** + * Creates a directed edge source → observer. + * Implementations must not check for duplicates or cycles. + */ + addObserver = (source: GraphNode, observer: GraphNode): GraphEdge => + linkSourceToObserverUnsafe(source, observer); + + /** + * Removes the directed edge source → observer, if it exists. + * Otherwise a no-op. + */ + removeObserver = (source: GraphNode, observer: GraphNode): void => + unlinkSourceFromObserverUnsafe(source, observer); + + /** + * Enumerates all observers of the given node. + * This uses the intrusive linked list stored in GraphNode. + * Complexity: O(k), where k = out-degree. + * No allocations. + */ + forEachObserver = ( + node: GraphNode, + fn: (observer: GraphNode) => void, + ): void => { + for (let e = node.firstOut; e !== null; e = e.nextOut) fn(e.to); + }; + + /** + * Enumerates all sources of the given node. + * This uses the intrusive linked list stored in GraphNode. + * Complexity: O(k), where k = in-degree. + * No allocations. + */ + forEachSource = (node: GraphNode, fn: (source: GraphNode) => void): void => { + for (let e = node.firstIn; e !== null; e = e.nextIn) fn(e.from); + }; + + /** + * Returns true iff observer is present in the outgoing adjacency list + * of the source node. + */ + hasObserver = (source: GraphNode, observer: GraphNode) => + hasObserverUnsafe(source, observer); + + /** + * Returns true iff source is present in the incoming adjacency list + * of the observer node. + */ + hasSource = (source: GraphNode, observer: GraphNode): boolean => + hasSourceUnsafe(source, observer); + + /** + * Re-binds the observer to a new source node. + * + * Useful for effect re-tracking in reactive runtimes: + * + * oldSource → observer (removed) + * newSource → observer (added) + * + * Must remain O(1) amortized. + */ + replaceSource = ( + oldSource: GraphNode, + newSource: GraphNode, + observer: GraphNode, + ): void => replaceSourceUnsafe(oldSource, newSource, observer); +} diff --git a/packages/@reflex/core/src/graph/process/graph.constants.ts b/packages/@reflex/core/src/graph/process/graph.constants.ts new file mode 100644 index 0000000..5f7f1f5 --- /dev/null +++ b/packages/@reflex/core/src/graph/process/graph.constants.ts @@ -0,0 +1,42 @@ + +const CLEAN = 0; +const CHECK = 1 << 0; +const DIRTY = 1 << 1; +const DISPOSED = 1 << 2; +const DISPOSING = 1 << 3; +const SCHEDULED = 1 << 4; +const RUNNING = 1 << 5; +const ASYNC = 1 << 6; +const KIND_SOURCE = 1 << 7; +const KIND_COMPUTATION = 1 << 8; +const KIND_EFFECT = 1 << 9; + +/** + * Number of cells in the internal Uint32Array structures. + * + * - COUNTER_CELLS: [epoch, version, uversion] + */ +const COUNTER_CELLS = { + epoch: 0, + version: 1, + uversion: 2, + // async + generation: 3, + token: 4, +} as const; + +const COUNTER_CELLS_LENGTH = 5; + +export { + COUNTER_CELLS, + COUNTER_CELLS_LENGTH, + CLEAN, + DIRTY, + DISPOSED, + SCHEDULED, + RUNNING, + ASYNC, + KIND_SOURCE, + KIND_COMPUTATION, + KIND_EFFECT, +}; diff --git a/packages/@reflex/core/src/graph/process/graph.methods.ts b/packages/@reflex/core/src/graph/process/graph.methods.ts new file mode 100644 index 0000000..058d889 --- /dev/null +++ b/packages/@reflex/core/src/graph/process/graph.methods.ts @@ -0,0 +1,363 @@ +import { GraphEdge, GraphNode } from "./graph.node"; + +/** + * + * linkSourceToObserverUnsafe + * + * + * Creates a new directed edge: source → observer + * + * This function mutates *two* intrusive doubly-linked adjacency lists: + * + * OUT list of source: + * source.firstOut → ... → source.lastOut → (new edge) + * + * IN list of observer: + * observer.firstIn → ... → observer.lastIn → (new edge) + * + * Invariants after insertion: + * - source.lastOut === newly created edge + * - observer.lastIn === newly created edge + * - counts (outCount, inCount) are incremented + * + * Safety: + * - No duplicate detection. + * - No cycle detection. + * - Caller is responsible for correctness. + * + * Complexity: O(1) + */ +export const linkSourceToObserverUnsafe = ( + source: GraphNode, + observer: GraphNode, +): GraphEdge => { + const edge = new GraphEdge(source, observer); + + // ----- OUT adjacency (source → observer) + const lastOut = source.lastOut; + edge.prevOut = lastOut; + edge.nextOut = null; + + if (lastOut === null) source.firstOut = edge; + else lastOut.nextOut = edge; + + source.lastOut = edge; + source.outCount++; + + // ----- IN adjacency (source → observer) + const lastIn = observer.lastIn; + edge.prevIn = lastIn; + edge.nextIn = null; + + if (lastIn === null) observer.firstIn = edge; + else lastIn.nextIn = edge; + + observer.lastIn = edge; + observer.inCount++; + + return edge; +}; + +/** + * + * unlinkEdgeUnsafe + * + * + * Removes a single directed edge from *both* + * intrusive adjacency lists: + * + * OUT list of edge.from + * IN list of edge.to + * + * Invariants after unlink: + * - All list pointers remain consistent. + * - Counts of both nodes are decremented. + * - Edge's own pointers are nulled for safety / GC friendliness. + * + * Safety: + * - Caller guarantees that 'edge' is present in both lists. + * + * Complexity: O(1) + */ +export const unlinkEdgeUnsafe = (edge: GraphEdge): void => { + const from = edge.from; + const to = edge.to; + + // ----- OUT adjacency unlink + const prevOut = edge.prevOut; + const nextOut = edge.nextOut; + + if (prevOut !== null) prevOut.nextOut = nextOut; + else from.firstOut = nextOut; + + if (nextOut !== null) nextOut.prevOut = prevOut; + else from.lastOut = prevOut; + + from.outCount--; + + // ----- IN adjacency unlink + const prevIn = edge.prevIn; + const nextIn = edge.nextIn; + + if (prevIn !== null) prevIn.nextIn = nextIn; + else to.firstIn = nextIn; + + if (nextIn !== null) nextIn.prevIn = prevIn; + else to.lastIn = prevIn; + + to.inCount--; + + // Cleanup (edge becomes detached and cannot be reused accidentally) + edge.prevOut = edge.nextOut = null; + edge.prevIn = edge.nextIn = null; +}; + +/** + * + * unlinkSourceFromObserverUnsafe + * + * + * Removes the *first* occurrence of an edge `source → observer`. + * If no such edge exists, this is a no-op. + * + * Complexity: O(k), where k = out-degree of source. + * + * Safety: + * - UNSAFE: no validation, no consistency checks. + */ +export const unlinkSourceFromObserverUnsafe = ( + source: GraphNode, + observer: GraphNode, +): void => { + let edge = source.firstOut; + + while (edge !== null) { + if (edge.to === observer) { + unlinkEdgeUnsafe(edge); + return; + } + edge = edge.nextOut; + } + + // No edge found — silently ignore. +}; + +/** + * + * linkSourceToObserversBatchUnsafe + * + * + * Bulk version of adding multiple edges: + * source → observer[i] + * + * Returns an array of created edges. + * + * Complexity: O(n), where n = observers.length + * Allocates exactly one array and N edges. + */ +export const linkSourceToObserversBatchUnsafe = ( + source: GraphNode, + observers: readonly GraphNode[], +): GraphEdge[] => { + const n = observers.length; + if (n === 0) return []; + + const edges = new Array(n); + + for (let i = 0; i < n; i++) { + const observer = observers[i]!; + edges[i] = linkSourceToObserverUnsafe(source, observer); + } + + return edges; +}; + +/** + * + * unlinkAllObserversUnsafe + * + * + * Removes *all* outgoing edges from the given node: + * node → observer* + * + * This is the simple single-pass version. Mutations happen during traversal. + * + * Complexity: O(k), where k = out-degree. + */ +export const unlinkAllObserversUnsafe = (source: GraphNode): void => { + let edge = source.firstOut; + + while (edge !== null) { + const next = edge.nextOut; + unlinkEdgeUnsafe(edge); + edge = next; + } +}; + +/** + * + * unlinkAllSourcesUnsafe + * + * + * Removes *all* incoming edges to the given node: + * source* → node + * + * Complexity: O(k), where k = in-degree. + */ +export const unlinkAllSourcesUnsafe = (observer: GraphNode): void => { + let edge = observer.firstIn; + + while (edge !== null) { + const next = edge.nextIn; + unlinkEdgeUnsafe(edge); + edge = next; + } +}; + +/** + * + * unlinkAllObserversChunkedUnsafe + * + * + * Two-pass version of unlinking: + * (1) Snapshot edges into an array + * (2) Unlink them in reverse order + * + * This avoids traversal inconsistencies when unlinking during iteration. + * Recommended when removing many edges at once. + */ +export const unlinkAllObserversChunkedUnsafe = (source: GraphNode): void => { + const count = source.outCount; + if (count === 0) return; + + if (count === 1) { + unlinkEdgeUnsafe(source.firstOut!); + return; + } + + const edges = new Array(count); + + let idx = 0; + let edge = source.firstOut; + + while (edge !== null) { + edges[idx++] = edge; + edge = edge.nextOut; + } + + for (let i = count - 1; i >= 0; i--) unlinkEdgeUnsafe(edges[i]!); +}; + +/** + * + * unlinkAllSourcesChunkedUnsafe + * + * + * Chunked reverse-unlinking for incoming edges. + * Same rationale as unlinkAllObserversChunkedUnsafe. + */ +export const unlinkAllSourcesChunkedUnsafe = (observer: GraphNode): void => { + const count = observer.inCount; + if (count === 0) return; + + if (count === 1) { + unlinkEdgeUnsafe(observer.firstIn!); + return; + } + + const edges = new Array(count); + + let idx = 0; + let edge = observer.firstIn; + + while (edge !== null) { + edges[idx++] = edge; + edge = edge.nextIn; + } + + for (let i = count - 1; i >= 0; i--) unlinkEdgeUnsafe(edges[i]!); +}; + +/** + * + * unlinkAllObserversBulkUnsafeForDisposal + * + * + * Alias for the chunked unlink strategy. + * Intended for "node disposal" operations where maximal unlink throughput + * is required and edge order does not matter. + */ +export const unlinkAllObserversBulkUnsafeForDisposal = ( + source: GraphNode, +): void => { + unlinkAllObserversChunkedUnsafe(source); +}; + +/** + * + * hasSourceUnsafe + * + * + * Returns true if an edge exists: + * source → observer + * + * Complexity: O(k), where k = out-degree of source. + */ +export const hasSourceUnsafe = ( + source: GraphNode, + observer: GraphNode, +): boolean => { + let edge = source.firstOut; + while (edge !== null) { + if (edge.to === observer) return true; + edge = edge.nextOut; + } + return false; +}; + +/** + * + * hasObserverUnsafe + * + * + * Returns true if an edge exists: + * source → observer + * + * But traversing the IN-list of the observer. + * + * Complexity: O(k), where k = in-degree of observer. + */ +export const hasObserverUnsafe = ( + source: GraphNode, + observer: GraphNode, +): boolean => { + let edge = observer.firstIn; + while (edge !== null) { + if (edge.from === source) return true; + edge = edge.nextIn; + } + return false; +}; + +/** + * + * replaceSourceUnsafe + * + * + * Performs an atomic rebinding of a dependency: + * + * oldSource → observer (removed) + * newSource → observer (added) + * + * Used during reactive effect re-tracking. + * + * Complexity: O(k), due to scan of oldSource's out-list. + */ +export const replaceSourceUnsafe = ( + oldSource: GraphNode, + newSource: GraphNode, + observer: GraphNode, +): void => { + unlinkSourceFromObserverUnsafe(oldSource, observer); + linkSourceToObserverUnsafe(newSource, observer); +}; diff --git a/packages/@reflex/core/src/graph/process/graph.node.ts b/packages/@reflex/core/src/graph/process/graph.node.ts new file mode 100644 index 0000000..0b5b861 --- /dev/null +++ b/packages/@reflex/core/src/graph/process/graph.node.ts @@ -0,0 +1,133 @@ +import { INITIAL_CAUSATION } from "../../storage/config/causal.phase"; +import { CausalCoords } from "../../storage/config/CausalCoords"; +import { CLEAN } from "./graph.constants"; + +type NodeIndex = number; + +const NON_EXIST: NodeIndex = -1; + +/** + * GraphEdge + * = + * + * Intrusive bi-directional edge connecting two GraphNodes: + * + * from ---> to + * + * The edge participates in two separate intrusive doubly-linked lists: + * + * 1) OUT adjacency of `from`: + * from.firstOut → ... → edge → ... → from.lastOut + * + * 2) IN adjacency of `to`: + * to.firstIn → ... → edge → ... → to.lastIn + * + * These lists are stored *inside* GraphNode, not in GraphService or graph + * containers. This keeps mutation O(1), minimizes allocations, and provides + * tight control required by the runtime. + * + * Each edge tracks four pointers: + * prevOut, nextOut — outgoing adjacency chain + * prevIn, nextIn — incoming adjacency chain + * + * No extra metadata is stored: no weights, timestamps, or flags. The edge is + * as small and cheap as possible. + */ +class GraphEdge { + /** Source node of the edge */ + from: GraphNode; + /** Target node of the edge */ + to: GraphNode; + /** Previous edge in the outgoing list of `from` */ + prevOut: GraphEdge | null = null; + /** Next edge in the outgoing list of `from` */ + nextOut: GraphEdge | null = null; + /** Previous edge in the incoming list of `to` */ + prevIn: GraphEdge | null = null; + /** Next edge in the incoming list of `to` */ + nextIn: GraphEdge | null = null; + + constructor(from: GraphNode, to: GraphNode) { + this.from = from; + this.to = to; + } +} + +/** + * GraphNode + * = + * + * A node in the reactive dependency graph. + * This is a fully *intrusive* node: it stores all adjacency lists internally. + * + * STRUCTURE: + * ---------------------------------------------------------------------------- + * Outgoing edges (dependencies *from* this node): + * firstOut → ... → lastOut + * + * Incoming edges (dependencies *to* this node): + * firstIn → ... → lastIn + * + * These two lists are independent and form a bipartite representation of + * directional connections: out-edges represent observers, in-edges represent + * sources. + * + * INVARIANTS: + * ---------------------------------------------------------------------------- + * - If firstOut === null, then lastOut === null and outCount = 0. + * - If firstIn === null, then lastIn === null and inCount = 0. + * - Counts must always reflect the actual length of adjacency lists. + * - Edges must always form valid doubly-linked chains. + * + * FLAGS: + * ---------------------------------------------------------------------------- + * Node-level state flags are stored in `flags` using a BitMask. + * Typical use-cases: + * - CLEAN / DIRTY reactivity state + * - scheduler marks + * - GC / disposal hints + * + * The graph itself does not interpret these flags — external systems do. + * + * PERFORMANCE NOTES: + * ---------------------------------------------------------------------------- + * - GraphNode is shape-stable: all fields are allocated and initialized + * in the constructor to ensure V8 IC predictability. + * - All adjacency updates are O(1). + * - No arrays or extra memory structures are allocated during edge edits. + */ +class GraphNode { + /** Index in the causal layout (t/v/g/s table), or NON_EXIST */ + id: NodeIndex = NON_EXIST; + /** First outgoing dependency (this → observer) */ + firstOut: GraphEdge | null = null; + /** Last outgoing dependency (this → observer) */ + lastOut: GraphEdge | null = null; + /** First incoming dependency (source → this) */ + firstIn: GraphEdge | null = null; + /** Last incoming dependency (source → this) */ + lastIn: GraphEdge | null = null; + /** Number of outgoing edges */ + outCount: number = 0; + /** Number of incoming edges */ + inCount: number = 0; + /** + * Bit-mask for node-level flags. + * Initial state: CLEAN (defined in graph.constants). + */ + flags: number = CLEAN; + + causal: CausalCoords = { + t: INITIAL_CAUSATION, + v: INITIAL_CAUSATION, + g: INITIAL_CAUSATION, + s: INITIAL_CAUSATION, + }; + + constructor(id: NodeIndex) { + this.id = id; + } +} + +export { GraphNode, GraphEdge }; +export type { NodeIndex }; diff --git a/third-party/rigidify/src/index.ts b/packages/@reflex/core/src/index.ts similarity index 100% rename from third-party/rigidify/src/index.ts rename to packages/@reflex/core/src/index.ts diff --git a/packages/reflex/src/core/ownership/Readme.md b/packages/@reflex/core/src/ownership/Readme.md similarity index 100% rename from packages/reflex/src/core/ownership/Readme.md rename to packages/@reflex/core/src/ownership/Readme.md diff --git a/packages/@reflex/core/src/ownership/ownership.context.ts b/packages/@reflex/core/src/ownership/ownership.context.ts new file mode 100644 index 0000000..55c72e4 --- /dev/null +++ b/packages/@reflex/core/src/ownership/ownership.context.ts @@ -0,0 +1,53 @@ +import { IOwnershipContextRecord, ContextKeyType } from "./ownership.contract"; +import type { OwnershipNode } from "./ownership.node"; + +/** + * Create a new context layer inheriting from parent (if any). + * Root contexts use null-prototype objects. + */ +export function createContextLayer( + parent: IOwnershipContextRecord | null, +): IOwnershipContextRecord { + return parent ? Object.create(parent) : Object.create(null); +} + +/** + * Provide a key/value pair into a context object. + */ +export function contextProvide( + ctx: IOwnershipContextRecord, + key: ContextKeyType, + value: unknown, +): void { + ctx[key] = value; +} + +/** + * Walk up the ownership chain and lookup a context value by key. + */ +export function contextLookup( + node: OwnershipNode, + key: ContextKeyType, +): T | undefined { + let current: OwnershipNode | null = node; + + while (current !== null) { + const ctx = current._context; + if (ctx !== null && key in ctx) { + return ctx[key] as T; + } + current = current._parent; + } + + return undefined; +} + +/** + * Check if key exists as an own property in the given context. + */ +export function contextHasOwn( + ctx: IOwnershipContextRecord | null, + key: ContextKeyType, +): boolean { + return ctx !== null && Object.hasOwn(ctx, key); +} diff --git a/packages/@reflex/core/src/ownership/ownership.contract.ts b/packages/@reflex/core/src/ownership/ownership.contract.ts new file mode 100644 index 0000000..31050c6 --- /dev/null +++ b/packages/@reflex/core/src/ownership/ownership.contract.ts @@ -0,0 +1,40 @@ +type ContextKeyType = string; + +interface IOwnershipContextRecord { + [key: ContextKeyType]: unknown; +} + +interface IOwnershipContext { + readonly id: symbol; + readonly defaultValue?: T; +} + +interface IOwnership { + onScopeMount(fn: () => void): void; + onScopeCleanup(fn: () => void): void; + + dispose(): void; + + provide(key: ContextKeyType, value: unknown): void; + inject(key: ContextKeyType): T | undefined; + hasOwn(key: ContextKeyType): boolean; +} + +interface ICleanupScope { + onScopeCleanup(fn: () => void): void; +} + +interface IContextAccess { + provide(key: ContextKeyType, value: unknown): void; + inject(key: ContextKeyType): T | undefined; + hasOwn(key: ContextKeyType): boolean; +} + +export type { + ContextKeyType, + IOwnershipContextRecord, + IOwnershipContext, + IOwnership, + ICleanupScope, + IContextAccess, +}; diff --git a/packages/@reflex/core/src/ownership/ownership.node.ts b/packages/@reflex/core/src/ownership/ownership.node.ts new file mode 100644 index 0000000..7e031ff --- /dev/null +++ b/packages/@reflex/core/src/ownership/ownership.node.ts @@ -0,0 +1,211 @@ +// ownership.node.ts + +/** + * @file ownership.node.ts + * + * Optimized OwnershipNode class with fixed layout and prototype methods. + * + * Layout: + * - tree links: _parent, _firstChild, _lastChild, _nextSibling, _prevSibling + * - context: _context (lazy, via prototype chain) + * - cleanups: _cleanups (lazy) + * - counters: _childCount, _flags, _epoch, _contextEpoch + */ + +import { DISPOSED } from "../graph/process/graph.constants"; +import { CausalCoords } from "../storage/config/CausalCoords"; +import { + createContextLayer, + contextProvide, + contextLookup, + contextHasOwn, +} from "./ownership.context"; +import type { + ContextKeyType, + IOwnershipContextRecord, +} from "./ownership.contract"; + +export class OwnershipNode { + _parent: OwnershipNode | null = null; + _firstChild: OwnershipNode | null = null; + _lastChild: OwnershipNode | null = null; + _nextSibling: OwnershipNode | null = null; + _prevSibling: OwnershipNode | null = null; + + // payload + _context: IOwnershipContextRecord | null = null; + _cleanups: NoneToVoidFn[] | null = null; + + // state + _childCount = 0; + _flags = 0; + + // flat causal coords (even if unused yet) + _causal: CausalCoords = { + t: 0, + v: 0, + g: 0, + s: 0, + }; +} + +export class OwnershipService { + createOwner = (parent: OwnershipNode | null = null): OwnershipNode => { + const node = new OwnershipNode(); + if (parent !== null) this.appendChild(parent, node); + return node; + }; + + appendChild = (parent: OwnershipNode, child: OwnershipNode): void => { + if (parent._flags & DISPOSED) return; + + // SAFE reparent + const oldParent = child._parent; + if (oldParent !== null) { + this.removeChild(oldParent, child); + } + + child._parent = parent; + child._prevSibling = parent._lastChild; + child._nextSibling = null; + + if (parent._lastChild !== null) { + parent._lastChild._nextSibling = child; + } else { + parent._firstChild = child; + } + + parent._lastChild = child; + parent._childCount++; + }; + + removeChild = (parent: OwnershipNode, child: OwnershipNode): void => { + if (child._parent !== parent) return; + if (parent._flags & DISPOSED) return; + + const prev = child._prevSibling; + const next = child._nextSibling; + + if (prev !== null) prev._nextSibling = next; + else parent._firstChild = next; + + if (next !== null) next._prevSibling = prev; + else parent._lastChild = prev; + + child._parent = null; + child._prevSibling = null; + child._nextSibling = null; + + parent._childCount--; + }; + + dispose = (root: OwnershipNode): void => { + if (root._flags & DISPOSED) return; + + let node: OwnershipNode | null = root; + + while (node !== null) { + const last: OwnershipNode | null = node._lastChild; + + if (last !== null && !(last._flags & DISPOSED)) { + node = last; + continue; + } + + const parent: OwnershipNode | null = node._parent; + + // run cleanups (LIFO) + const cleanups = node._cleanups; + node._cleanups = null; + + if (cleanups !== null) { + for (let i = cleanups.length - 1; i >= 0; i--) { + try { + cleanups[i]?.(); + } catch (err) { + console.error("Error during ownership cleanup:", err); + } + } + } + + node._flags = DISPOSED; + + if (parent !== null) { + const prev = node._prevSibling; + const next = node._nextSibling; + + if (prev !== null) prev._nextSibling = next; + else parent._firstChild = next; + + if (next !== null) next._prevSibling = prev; + else parent._lastChild = prev; + + parent._childCount--; + } + + // reset node + node._parent = null; + node._firstChild = null; + node._lastChild = null; + node._nextSibling = null; + node._prevSibling = null; + node._context = null; + node._childCount = 0; + + node = parent; + } + }; + + /* ───────────── Context ───────────── */ + + getContext = (node: OwnershipNode): IOwnershipContextRecord => { + let ctx = node._context; + if (ctx !== null) return ctx; + + ctx = createContextLayer(node._parent?._context ?? null); + node._context = ctx; + return ctx; + }; + + provide = ( + node: OwnershipNode, + key: ContextKeyType, + value: unknown, + ): void => { + const FORBIDDEN_KEYS = new Set(["__proto__", "prototype", "constructor"]); + + if (value === node) { + throw new Error("Cannot provide owner itself"); + } + + if (typeof key === "string" && FORBIDDEN_KEYS.has(key)) { + throw new Error(`Forbidden context key: ${key}`); + } + + contextProvide(this.getContext(node), key, value); + }; + + inject = (node: OwnershipNode, key: ContextKeyType): T | undefined => { + return contextLookup(node, key); + }; + + hasOwn = (node: OwnershipNode, key: ContextKeyType): boolean => { + const ctx = node._context; + return ctx !== null && contextHasOwn(ctx, key); + }; + + onScopeCleanup = (node: OwnershipNode, fn: NoneToVoidFn): void => { + if (node._flags & DISPOSED) return; + + let arr = node._cleanups; + if (arr === null) { + arr = []; + node._cleanups = arr; + } + arr.push(fn); + }; +} + +type IOwnership = OwnershipService; + +export type { IOwnership }; diff --git a/packages/@reflex/core/src/ownership/ownership.scope.ts b/packages/@reflex/core/src/ownership/ownership.scope.ts new file mode 100644 index 0000000..2ed2bd0 --- /dev/null +++ b/packages/@reflex/core/src/ownership/ownership.scope.ts @@ -0,0 +1,65 @@ +import { OwnershipNode, OwnershipService } from "./ownership.node"; + +/** + * OwnershipScope + * + * Maintains the current ownership context (stack-like), + * without owning lifecycle or disposal responsibilities. + * + * Responsibilities: + * - track current OwnershipNode + * - provide safe withOwner switching + * - create scoped owners via OwnershipService + */ +export class OwnershipScope { + private _current: OwnershipNode | null = null; + private readonly _service: OwnershipService; + + constructor(service: OwnershipService) { + this._service = service; + } + + getOwner(): OwnershipNode | null { + return this._current; + } + + withOwner(owner: OwnershipNode, fn: () => T): T { + const prev = this._current; + this._current = owner; + + try { + return fn(); + } finally { + this._current = prev; + } + } + + /** + * Create a new ownership scope. + * + * - Parent defaults to current owner + * - Does NOT auto-dispose the owner + * (lifecycle is managed elsewhere) + */ + createScope( + fn: () => T, + parent: OwnershipNode | null = this._current, + ): T { + const owner = this._service.createOwner(parent); + return this.withOwner(owner, fn); + } +} + +/** + * Factory for creating a new OwnershipScope instance. + * + * OwnershipService is injected explicitly to avoid globals + * and enable deterministic ownership graphs. + */ +export function createOwnershipScope( + service: OwnershipService, +): OwnershipScope { + return new OwnershipScope(service); +} + +export type { OwnershipScope as OwnershipScopeType }; diff --git a/packages/@reflex/core/src/shared/README.md b/packages/@reflex/core/src/shared/README.md new file mode 100644 index 0000000..7562d62 --- /dev/null +++ b/packages/@reflex/core/src/shared/README.md @@ -0,0 +1,3 @@ +Shared — common utilities, constants, and type definitions. +Provides assert helpers, inline functions, and global types. +Used across multiple modules without introducing dependencies. \ No newline at end of file diff --git a/packages/@reflex/core/src/shared/types/async.d.ts b/packages/@reflex/core/src/shared/types/async.d.ts new file mode 100644 index 0000000..1c27d64 --- /dev/null +++ b/packages/@reflex/core/src/shared/types/async.d.ts @@ -0,0 +1,68 @@ +/** + * Represents a minimal unified contract for asynchronous values, + * compatible with both native Promises and custom reactive runtimes. + * + * A `Thenable` behaves like a Promise but may also expose internal state + * for inspection or integration with reactive graphs. + * + * This interface allows `await` compatibility without forcing the value + * to be a native Promise, enabling fine-grained async reactivity. + * + * @template T Type of the resolved value. + */ +interface Thenable { + /** + * Attaches callbacks for the resolution or rejection of the asynchronous value. + * + * The callbacks can return either a plain value or another `Async` + * (which includes native Promises and custom Thenables). + * This preserves the full "Promise resolution" semantics while allowing + * runtime extensions (e.g. lazy evaluation or reactive propagation). + * + * @typeParam TResult1 - Type returned on successful resolution. + * @typeParam TResult2 - Type returned on rejection. + * + * @param onfulfilled Callback invoked when the computation resolves successfully. + * May return a value or another Async computation. + * + * @param onrejected Callback invoked when the computation is rejected. + * May return a recovery value or another Async computation. + * + * @returns A new `Thenable` representing the continuation of the chain. + */ + then( + onfulfilled?: ((value: T) => TResult1 | Async) | null, + onrejected?: ((reason: unknown) => TResult2 | Async) | null + ): Thenable; + + /** + * Optional runtime state indicator for reactive or diagnostic purposes. + * Not part of the standard PromiseLike contract, but useful for + * observing internal progress without attaching callbacks. + * + * - `"pending"` → The computation has not yet settled. + * - `"fulfilled"` → The computation completed successfully. + * - `"rejected"` → The computation failed. + */ + readonly state?: "pending" | "fulfilled" | "rejected" + + /** + * The resolved value of the computation (if available). + * Typically undefined until `state` becomes `"fulfilled"`. + */ + readonly value?: T; + + /** + * The reason of failure, if `state` is `"rejected"`. + */ + readonly reason?: unknown; +} + +/** + * Unified alias for any asynchronous computation, + * whether native (`Promise`) or user-defined (`Thenable`). + * + * Can be used in APIs to accept both native Promises + * and extended asynchronous abstractions transparently. + */ +type Async = Promise | Thenable; diff --git a/packages/@reflex/core/src/shared/types/declarations.d.ts b/packages/@reflex/core/src/shared/types/declarations.d.ts new file mode 100644 index 0000000..f6440d3 --- /dev/null +++ b/packages/@reflex/core/src/shared/types/declarations.d.ts @@ -0,0 +1,29 @@ +declare const API_PROTOCOL_VERSION: `${number}.${number}.${number}`; + +declare const APP_VERSION: string; +declare const APP_REVISION: string; + +declare const BUILD_MODE: "development" | "production" | "test" +declare const PLATFORM: "browser" | "node" | "worker" + +declare const __DEV__: boolean; + +declare namespace ReflexGlobal { + const __REFLEX_LIB__: Record; + + const __REFLEX_INSPECTOR__: Record | undefined; + + const __REFLEX_FEATURE_FLAGS__: Readonly> | undefined; + + const __REFLEX_RUNTIME__: Readonly<{ + startTime: number; + activeOwners: number; + dirtyNodes: number; + }>; + + const __REFLEX_LOGGER__: Readonly<{ + info: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; + }>; +} diff --git a/packages/@reflex/core/src/shared/types/globals.d.ts b/packages/@reflex/core/src/shared/types/globals.d.ts new file mode 100644 index 0000000..a6369a5 --- /dev/null +++ b/packages/@reflex/core/src/shared/types/globals.d.ts @@ -0,0 +1,57 @@ +/** + * A function that never returns (e.g., throws or loops forever). + */ +type Nothing = () => never; + +type NoneToVoidFn = () => void; + +/** + * A function that takes an argument of type T and returns nothing. + * Useful for callback hooks, observers, disposers etc. + */ +type OneToVoidFn = (value: T) => void; + +/** + * Extracts the type of the first parameter of a function type F. + * If F doesn't take parameters, resolves to never. + */ +type FirstArg = F extends (arg: infer A, ...rest: any[]) => any ? A : never; + +/** + * Converts a union type U to an intersection type. + * Useful for merging multiple contract types into one. + */ +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I +) => void + ? I + : never; + +/** + * Creates a type where exactly one of the keys K in T must be present + * (mutually exclusive keys). Useful for discriminated unions of config objects. + */ +type RequireOnlyOne = K extends keyof T + ? { [P in K]: T[P] } & Partial, never>> + : never; + +/** + * A signal-like accessor: no params returns a value of type T. + * Useful if you want a consistent “getter” type in your reactive system. + */ +type Accessor = { + get value(): T; +}; + +/** + * A mutator paired with an accessor: returns void and sets value of type T. + * Often seen in APIs like `createSignal() => [Accessor, Setter]`. + */ +type Setter = (value: T) => void; + +/** + * A tuple of accessor and setter for type T. + */ +type Signal = [Accessor, Setter]; + + diff --git a/packages/@reflex/core/src/shared/types/queue.d.ts b/packages/@reflex/core/src/shared/types/queue.d.ts new file mode 100644 index 0000000..446ca0e --- /dev/null +++ b/packages/@reflex/core/src/shared/types/queue.d.ts @@ -0,0 +1,13 @@ +interface Queueable { + enqueue(value: T): void | Async; + dequeue(): T | undefined | Async; + isEmpty?(): boolean; + clear?(): void; + close?(): void; + [Symbol.asyncIterator]?(): AsyncIterator; +} + +interface QueueableLike { + enqueue?: (value: T) => unknown; + dequeue?: () => T | Async | undefined; +} diff --git a/packages/@reflex/core/src/storage/compare/compare64.ts b/packages/@reflex/core/src/storage/compare/compare64.ts new file mode 100644 index 0000000..ef24670 --- /dev/null +++ b/packages/@reflex/core/src/storage/compare/compare64.ts @@ -0,0 +1,20 @@ +export function compare64( + ahi: number, + alo: number, + bhi: number, + blo: number, +): number { + ahi >>>= 0; + bhi >>>= 0; + + if (ahi < bhi) return -1; + if (ahi > bhi) return 1; + + alo >>>= 0; + blo >>>= 0; + + if (alo < blo) return -1; + if (alo > blo) return 1; + + return 0; +} diff --git a/packages/@reflex/core/src/storage/compare/compareWrap.ts b/packages/@reflex/core/src/storage/compare/compareWrap.ts new file mode 100644 index 0000000..b2ee704 --- /dev/null +++ b/packages/@reflex/core/src/storage/compare/compareWrap.ts @@ -0,0 +1,9 @@ +export function compareWrap(a: number, b: number, radius: number): number { + const diff = (b - a) | 0; + const over = ((diff + radius) & (2 * radius - 1)) - radius; + + const less = (over >> 31) & 1; + const greater = (-over >> 31) & 1; + + return greater - less; +} diff --git a/packages/@reflex/core/src/storage/config/CausalCoords.ts b/packages/@reflex/core/src/storage/config/CausalCoords.ts new file mode 100644 index 0000000..f0ec2f5 --- /dev/null +++ b/packages/@reflex/core/src/storage/config/CausalCoords.ts @@ -0,0 +1,180 @@ +/** + * ============================================================ + * Causal Coordinates Space + * + * X₄ = T⁴ = S¹_t × S¹_v × S¹_g × S¹_s + * + * t — epoch (causal time), + * v — version (value evolution), + * g — generation (async layer), + * s — synergy / structural (graph topology). + * + * Дискретное представление: + * + * (t, v, g, s) ∈ ℤ / 2^{T_BITS}ℤ × ℤ / 2^{V_BITS}ℤ × ℤ / 2^{G_BITS}ℤ × ℤ / 2^{S_BITS}ℤ + * + * То есть каждое измерение — циклическая группа ℤ_{2^k} с операцией + * + * x ⊕ δ := (x + δ) mod 2^k. + * + * В коде это реализуется как: + * + * (x + δ) & (2^k - 1) + * + * что даёт wrap по модулю 2^k в 32-битном целочисленном представлении. + * + * ------------------------------------------------------------ + * Уровни упрощения геометрии: + * + * Level 0: Full Reactive Geometry (async + dynamic graph) + * + * X₄ = S¹_t × S¹_v × S¹_g × S¹_s + * | | | └─ s: structural / topology + * | | | | + * | | └─────── g: async generation + * | └────────────── v: version (value) + * └───────────────────── t: causal epoch + * + * Level 1: No async (strictly synchronous runtime) + * + * Constraint: execution order == causal order + * ⇒ g становится выводимым из t (нет независимого async-слоя) + * + * X₃(sync) = S¹_t × S¹_v × S¹_s + * + * Level 2: Static graph (no dynamic topology) + * + * Constraint: topology fixed, нет структурных изменений во время рантайма + * ⇒ s константа, не входит в динамическое состояние + * + * X₂(struct-sync) = S¹_t × S¹_v + * + * Level 3: Pure functional / timeless evaluation + * + * Constraint: только версии значений влияют на наблюдаемое поведение + * ⇒ t не влияет на вычисление (чистая функция по v) + * + * X₁(pure-value) = S¹_v + * + * Иерархия проекций (факторизация степени свободы): + * + * T⁴(t, v, g, s) + * ──[no async]────────▶ T³(t, v, s) + * ──[static graph]─▶ T²(t, v) + * ──[pure]──────▶ T¹(v) + * + * На уровне алгебры: + * + * T⁴ ≅ ℤ_{2^{T_BITS}} × ℤ_{2^{V_BITS}} × ℤ_{2^{G_BITS}} × ℤ_{2^{S_BITS}} + * T³, T², T¹ — проекции T⁴ с тем же покомпонентным законом сложения. + */ + +/** + * Дискретные каузальные координаты. + * + * Формально: + * (t, v, g, s) ∈ ℤ_{2^{T_BITS}} × ℤ_{2^{V_BITS}} × ℤ_{2^{G_BITS}} × ℤ_{2^{S_BITS}} + * + * Параметры T, V, G, S оставлены обобщёнными, чтобы при желании + * можно было использовать branded-типы: + * + * type Epoch = number & { readonly __tag: "Epoch" }; + * type Version = number & { readonly __tag: "Version" }; + * ... + */ +interface CausalCoords { + /** t — causal epoch, t ∈ ℤ_{2^{T_BITS}} */ + t: T; + /** v — value version, v ∈ ℤ_{2^{V_BITS}} */ + v: V; + /** g — async generation, g ∈ ℤ_{2^{G_BITS}} */ + g: G; + /** s — structural / topology, s ∈ ℤ_{2^{S_BITS}} */ + s: S; +} + +/** + * Полное пространство T⁴(t, v, g, s). + * + * Математически: + * T⁴ ≅ ℤ_{2^{T_BITS}} × ℤ_{2^{V_BITS}} × ℤ_{2^{G_BITS}} × ℤ_{2^{S_BITS}} + */ +type T4< + T extends number, + V extends number, + G extends number, + S extends number, +> = CausalCoords; + +/** + * T³(t, v, g) — проекция T⁴ без структурного измерения s. + * + * Используется, когда топология фиксирована или вынесена за пределы + * динамического состояния узла. + */ +type T3 = Pick< + CausalCoords, + "t" | "v" | "g" +>; + +/** + * T²(t, v) — ещё более жёсткое упрощение: нет async и нет динамической + * топологии в состоянии узла. + * + * Это соответствует синхронной модели со статическим графом: + * + * X₂ ≅ S¹_t × S¹_v. + */ +type T2 = Pick< + CausalCoords, + "t" | "v" +>; + +/** + * T¹(v) — чисто функциональный слой: только версии значений. + * + * X₁ ≅ S¹_v ≅ ℤ_{2^{V_BITS}} + */ +type T1 = Pick, "v">; + +/** + * Сложение по модулю 2^k: + * + * addWrap(x, δ, mask) = (x + δ) mod 2^k, + * + * где mask = 2^k - 1. + * + * На уровне групп: + * ℤ_{2^k} с операцией ⊕ задаётся как: + * + * x ⊕ δ := (x + δ) mod 2^k. + * + * В реализации: + * + * (x + δ) & mask + * + * при условии, что: + * - x уже нормализован: 0 ≤ x ≤ mask, + * - mask = 2^k - 1, 0 < k ≤ 31, + * - δ — 32-битное целое (может быть отрицательным). + * + * Отрицательные δ работают естественно за счёт представления two’s complement: + * x = 0, δ = -1 ⇒ (0 + (-1)) & mask = mask. + * + * Функция намеренно «тонкая»: + * — без ветвлений; + * — без проверок диапазонов; + * — всё в 32-битной целочисленной арифметике. + */ +export function addWrap( + x: A, + delta: number, + mask: number, +): A { + // mask предполагается уже вида (1 << bits) - 1 и лежит в uint32. + // Приводим x к числу, добавляем δ и заворачиваем по маске. + // (& mask) обеспечивает mod 2^k и выбрасывает старшие биты. + return (((x as number) + delta) & mask) as A; +} + +export type { CausalCoords, T1, T2, T3, T4 }; diff --git a/packages/@reflex/core/src/storage/config/causal.phase.ts b/packages/@reflex/core/src/storage/config/causal.phase.ts new file mode 100644 index 0000000..1ec2459 --- /dev/null +++ b/packages/@reflex/core/src/storage/config/causal.phase.ts @@ -0,0 +1,17 @@ +// CAUSALLY_STABLE Єдиний причинний простір, шов гладкий. +// GENERATION_DRIFT Розрив у async-поколіннях, але структура зберігається. +// TOPOLOGY_TENSION Локальна зміна топології DAG, можливе «перетягування шва». +// CAUSAL_CONFLICT Немає способу звести B і C у спільний причинний контекст. +// - Найнебезпечніша ситуація, але в той же час, найрідша + +const enum CausalPhase { + CAUSALLY_STABLE = 0, + GENERATION_DRIFT = 1, + TOPOLOGY_TENSION = 2, + CAUSAL_CONFLICT = 3, +} + +const WRAP_END = 0xffff_ffff >>> 0; +const INITIAL_CAUSATION = 0; + +export { CausalPhase, WRAP_END, INITIAL_CAUSATION }; diff --git a/packages/@reflex/core/src/storage/layout/layout.ts b/packages/@reflex/core/src/storage/layout/layout.ts new file mode 100644 index 0000000..da29ce7 --- /dev/null +++ b/packages/@reflex/core/src/storage/layout/layout.ts @@ -0,0 +1,37 @@ +import type { FieldSpec } from "./schema"; + +export interface FieldLayout { + readonly shift: number; + readonly bits: number; + readonly mask32: number; +} + +export interface Layout64< + TSchema extends Record = Record, +> { + readonly fields: { [K in keyof TSchema]: FieldLayout }; + readonly fieldNames: (keyof TSchema)[]; + readonly totalBits: number; +} + +export function createLayout64>( + schema: TSchema, +): Layout64 { + let shift = 0; + const fields = {} as { [K in keyof TSchema]: FieldLayout }; + const fieldNames: (keyof TSchema)[] = Object.keys(schema); + + for (const name of fieldNames) { + const bits = schema[name]!.bits; + const mask32 = bits >= 32 ? 0xffffffff : bits > 0 ? (1 << bits) - 1 : 0; + + fields[name] = { shift, bits, mask32 }; + shift += bits; + } + + if (shift > 64) { + throw new Error(`Layout64: totalBits=${shift} > 64`); + } + + return { fields, fieldNames, totalBits: shift }; +} diff --git a/packages/@reflex/core/src/storage/layout/schema.ts b/packages/@reflex/core/src/storage/layout/schema.ts new file mode 100644 index 0000000..0259273 --- /dev/null +++ b/packages/@reflex/core/src/storage/layout/schema.ts @@ -0,0 +1,11 @@ +export interface FieldSpec { + readonly bits: number; +} + +export const NodeSchema = { + epoch: { bits: 12 }, + version: { bits: 10 }, + generation: { bits: 10 }, + synergy: { bits: 28 }, + layoutId: { bits: 2 }, +} satisfies Record; diff --git a/packages/@reflex/core/src/storage/layout/tables.ts b/packages/@reflex/core/src/storage/layout/tables.ts new file mode 100644 index 0000000..22b1d96 --- /dev/null +++ b/packages/@reflex/core/src/storage/layout/tables.ts @@ -0,0 +1,53 @@ +import type { Layout64 } from "./layout"; + +export interface TablesSOA { + readonly count: number; + readonly loMask: Uint32Array; + readonly hiMask: Uint32Array; + readonly loShift: Uint8Array; + readonly hiShift: Uint8Array; +} + +export function prepareTables>( + layout: Layout64, +): TablesSOA { + const n = layout.fieldNames.length; + + const loMask = new Uint32Array(n); + const hiMask = new Uint32Array(n); + const loShift = new Uint8Array(n); + const hiShift = new Uint8Array(n); + + for (let i = 0; i < n; i++) { + const name = layout.fieldNames[i]!; + const f = layout.fields[name]; + + const start = f.shift; + const end = f.shift + f.bits; + + if (start < 32) { + if (end <= 32) { + loMask[i] = (f.mask32 << start) >>> 0; + hiMask[i] = 0; + loShift[i] = start; + hiShift[i] = 0; + } else { + const loPart = 32 - start; + const hiPart = f.bits - loPart; + + loMask[i] = (((1 << loPart) - 1) << start) >>> 0; + hiMask[i] = (1 << hiPart) - 1; + loShift[i] = start; + hiShift[i] = 0; + } + } else { + const hShift = start - 32; + loMask[i] = 0; + hiMask[i] = (f.mask32 << hShift) >>> 0; + loShift[i] = 0; + hiShift[i] = hShift; + } + } + + return { count: n, loMask, hiMask, loShift, hiShift }; +} diff --git a/packages/@reflex/core/src/storage/pack/pack64.ts b/packages/@reflex/core/src/storage/pack/pack64.ts new file mode 100644 index 0000000..034f98a --- /dev/null +++ b/packages/@reflex/core/src/storage/pack/pack64.ts @@ -0,0 +1,39 @@ +import type { TablesSOA } from "../layout/tables"; + +export function pack64( + block: Uint32Array, + out: { hi: number; lo: number }, + t: TablesSOA, +): void { + let lo = 0; + let hi = 0; + + for (let i = 0; i < t.count; i++) { + const v = block[i]! | 0; + lo |= (v << t.loShift[i]!) & t.loMask[i]!; + hi |= (v << t.hiShift[i]!) & t.hiMask[i]!; + } + + out.lo = lo >>> 0; + out.hi = hi >>> 0; +} + +export function pack64Into( + block: Uint32Array, + out: Uint32Array, + index: number, + t: TablesSOA, +): void { + let lo = 0; + let hi = 0; + + for (let i = 0; i < t.count; i++) { + const v = block[i]! | 0; + lo |= (v << t.loShift[i]!) & t.loMask[i]!; + hi |= (v << t.hiShift[i]!) & t.hiMask[i]!; + } + + const base = index << 1; + out[base] = hi >>> 0; + out[base + 1] = lo >>> 0; +} diff --git a/packages/@reflex/core/src/storage/pack/pack64x8.ts b/packages/@reflex/core/src/storage/pack/pack64x8.ts new file mode 100644 index 0000000..6a0e668 --- /dev/null +++ b/packages/@reflex/core/src/storage/pack/pack64x8.ts @@ -0,0 +1,23 @@ +import type { TablesSOA } from "../layout/tables"; + +export function pack64x8( + blocks: readonly Uint32Array[], + out: Uint32Array, + t: TablesSOA, +): void { + for (let lane = 0; lane < 8; lane++) { + const b = blocks[lane]!; + let lo = 0; + let hi = 0; + + for (let i = 0; i < t.count; i++) { + const v = b[i]! | 0; + lo |= (v << t.loShift[i]!) & t.loMask[i]!; + hi |= (v << t.hiShift[i]!) & t.hiMask[i]!; + } + + const base = lane << 1; + out[base] = hi >>> 0; + out[base + 1] = lo >>> 0; + } +} diff --git a/packages/@reflex/core/src/storage/pack/unpack64.ts b/packages/@reflex/core/src/storage/pack/unpack64.ts new file mode 100644 index 0000000..e0a2eff --- /dev/null +++ b/packages/@reflex/core/src/storage/pack/unpack64.ts @@ -0,0 +1,17 @@ +import type { TablesSOA } from "../layout/tables"; + +export function unpack64( + hi: number, + lo: number, + out: Uint32Array, + t: TablesSOA, +): void { + hi >>>= 0; + lo >>>= 0; + + for (let i = 0; i < t.count; i++) { + const vLo = (lo & t.loMask[i]!) >>> t.loShift[i]!; + const vHi = (hi & t.hiMask[i]!) >>> t.hiShift[i]!; + out[i] = (vLo | vHi) >>> 0; + } +} diff --git a/packages/@reflex/core/src/storage/storage.contract.ts b/packages/@reflex/core/src/storage/storage.contract.ts new file mode 100644 index 0000000..fd090a8 --- /dev/null +++ b/packages/@reflex/core/src/storage/storage.contract.ts @@ -0,0 +1,116 @@ +declare const U64_INTERLEAVED_BRAND: unique symbol; + +/** + * Interleaved backing store: + * [hi0, lo0, hi1, lo1, ..., hi(n-1), lo(n-1)] + * + * Runtime: 100% Uint32Array. + * TypeScript: nominal subtype for safety. + */ +export type U64InterleavedArray = Uint32Array & { + readonly [U64_INTERLEAVED_BRAND]: true; +}; + +declare const U64_INDEX_BRAND: unique symbol; + +/** + * Nominal index for 64-bit positions. Distinguishes + * “index in 64-bit words” from “index in Uint32Array”. + * + * Runtime: plain number. + */ +export type U64Index = number & { readonly [U64_INDEX_BRAND]: true }; + +export interface Uint64Storage { + /** Number of logically allocated 64-bit elements. */ + readonly size: number; + + /** Capacity measured in 64-bit elements (not Uint32 slots). */ + readonly capacity: number; + + /** Total memory usage in bytes. */ + readonly memoryUsage: number; + + /** + * Allocates a single zero-initialized 64-bit slot. + * Returns the ID of that slot. + */ + create(): number; + + /** + * Allocates `count` contiguous 64-bit slots. + * Returns the ID of the first allocated element. + */ + createBatch(count: number): number; + + /** + * Clears logical size (O(1)), but preserves allocated memory. + */ + clear(): void; + + /** Returns upper 32 bits of element at `id`. */ + rawHi(id: number): number; + + /** Returns lower 32 bits of element at `id`. */ + rawLo(id: number): number; + + /** Writes upper 32 bits. */ + setHi(id: number, hi: number): void; + + /** Writes lower 32 bits. */ + setLo(id: number, lo: number): void; + + /** + * Writes `(hi, lo)` pair in one offset computation. + */ + write(id: number, hi: number, lo: number): void; + + /** + * Reads value as JS Number (precision ≤ 2^53−1). + */ + readNumber(id: number): number; + + /** + * Writes JS Number into 64-bit slot. + * Negative coerces to 0, >2^53−1 saturates. + */ + writeNumber(id: number, value: number): void; + + /** + * Reads value as full-precision unsigned 64-bit BigInt. + * (Slow path.) + */ + readBigInt(id: number): bigint; + + /** + * Writes full 64-bit BigInt. + * Only lower 64 bits are stored. + */ + writeBigInt(id: number, value: bigint): void; + + /** + * Fills `[start, end)` with repeated `(hi, lo)` pair. + */ + fill(hi: number, lo: number, start?: number, end?: number): void; + + /** + * Copies `count` 64-bit elements from another storage. + */ + copyFrom( + source: Uint64Storage, + sourceStart?: number, + destStart?: number, + count?: number, + ): void; + + /** + * Returns the underlying interleaved Uint32Array view. + * Do not mutate `size` or `capacity` via this buffer. + */ + toUint32Array(): Uint32Array; + + /** + * Returns a no-copy Uint32Array view over a range of elements. + */ + subarray(start: number, end?: number): Uint32Array; +} diff --git a/packages/reflex/src/core/README.md b/packages/@reflex/core/src/storage/storage.runtime.ts similarity index 100% rename from packages/reflex/src/core/README.md rename to packages/@reflex/core/src/storage/storage.runtime.ts diff --git a/packages/@reflex/core/src/storage/storage.structure.ts b/packages/@reflex/core/src/storage/storage.structure.ts new file mode 100644 index 0000000..02d02c7 --- /dev/null +++ b/packages/@reflex/core/src/storage/storage.structure.ts @@ -0,0 +1,324 @@ +import { U64InterleavedArray, Uint64Storage } from "./storage.contract"; + +const TWO_32 = 4294967296; // 2^32 +const BIGINT_32 = 32n; +const BIGINT_U32_MASK = 0xffffffffn; +const BIGINT_MASK_64 = (1n << 64n) - 1n; +const TWO_NEG_32 = 2.3283064365386963e-10; + +/** + * A high-performance storage structure for 64-bit unsigned integers, + * implemented on top of `Uint32Array` using interleaved pairs `[hi, lo]`. + * + * This avoids the overhead of JavaScript `BigInt`, while retaining + * full 64-bit semantics via two 32-bit lanes: + * + * hi = upper 32 bits + * lo = lower 32 bits + * + * Memory layout: + * + * index: 0 1 2 3 4 5 6 7 ... + * └hi₀┘ └lo₀┘ └hi₁┘ └lo₁┘ └hi₂┘ └lo₂┘ └hi₃┘ └lo₃┘ ... + * + * For an array of states S in Uint32Array: + * each node i ∈ ℕ is represented by a pair of 32-bit words: + * + * NodeState₆₄(i) = (hiᵢ, loᵢ) + * where: + * hiᵢ = S[2·i] + * loᵢ = S[2·i + 1] + * + * that thereby, array S can be transformed to sequence of: + * S = [hi₀, lo₀, hi₁, lo₁, hi₂, lo₂, …] + * and each node occupies two adjacent indices. + * + * Features: + * - O(1) creation and write operations + * - cache-friendly interleaving pattern (proven fastest in V8) + * - no allocations during write/read + * - optional BigInt and Number conversions when needed + * - batch creation and fast bulk copying + * - linear memory buffer compatible with WASM and native bit ops + * + * This class is ideal for high-frequency low-level systems: + * reactive runtimes, schedulers, probabilistic structures, + * simulation engines, or causal-consistency models. + */ +export class Uint64Array implements Uint64Storage { + private _state: U64InterleavedArray; + private _size: number; + private _capacity: number; + + /** + * Creates a new Uint64 storage with the given initial capacity. + * + * @param capacity Number of 64-bit elements to allocate upfront. + * Real allocated memory = `capacity * 2 * 4 bytes`. + */ + constructor(capacity = 2048) { + const cap = capacity >>> 0; + + this._state = new Uint32Array(cap << 1) as U64InterleavedArray; + this._size = 0; + this._capacity = cap; + } + + toUint32Array(): U64InterleavedArray { + return this._state; + } + + /** + * Allocates a new 64-bit slot and returns its ID. + * The slot is zero-initialized (TypedArrays are zero-filled). + */ + create(): number { + const id = this._size; + if (id >= this._capacity) this._grow(); + this._size = id + 1; + return id; + } + + /** + * Allocates multiple IDs at once. + * + * @param count Number of elements to create. + * @returns ID of the first newly allocated element. + */ + createBatch(count: number): number { + const n = count >>> 0; + const startId = this._size; + const endId = startId + n; + + if (endId > this._capacity) { + // while зберігаємо на випадок дуже великих batch-ів, + // але в більшості випадків це одна ітерація. + while (endId > this._capacity) this._grow(); + } + + this._size = endId; + return startId; + } + + /** + * Ensures capacity is at least `requiredCapacity` elements. + * Useful to avoid multiple grow() calls in hot paths. + */ + reserve(requiredCapacity: number): void { + const needed = requiredCapacity >>> 0; + if (needed <= this._capacity) return; + + while (this._capacity < needed) this._grow(); + } + + /** Upper 32 bits for element `id`. */ + rawHi(id: number): number { + const base = id + id; + return this._state[base]!; + } + + /** Lower 32 bits for element `id`. */ + rawLo(id: number): number { + const base = id + id + 1; + return this._state[base]!; + } + + setHi(id: number, hi: number): void { + const base = id + id; + this._state[base] = hi >>> 0; + } + + setLo(id: number, lo: number): void { + const base = id + id + 1; + this._state[base] = lo >>> 0; + } + + /** + * Low-level write using precomputed base index (2 * id). + * Intended for hot loops that already know the base. + */ + writeRaw(baseIndex: number, hi: number, lo: number): void { + const s = this._state; + s[baseIndex] = hi >>> 0; + s[baseIndex + 1] = lo >>> 0; + } + + /** + * Writes a 64-bit value using two 32-bit lanes. + */ + write(id: number, hi: number, lo: number): void { + const b = id + id; // faster than id << 1 on V8 in tight loops + const s = this._state; + s[b] = hi >>> 0; + s[b + 1] = lo >>> 0; + } + + /** + * Reads the 64-bit value as a BigInt. + * Slow path – використовується рідко. + */ + readBigInt(id: number): bigint { + const base = id + id; + const state = this._state; + // Uint32Array already yields unsigned ints, нет смысла в >>> 0 + return (BigInt(state[base]!) << BIGINT_32) | BigInt(state[base + 1]!); + } + + /** + * Writes a 64-bit BigInt value into the storage. + * Slow path – зручно для інтеграцій, не для гарячих циклів. + */ + writeBigInt(id: number, value: bigint): void { + // Нормалізуємо до 64-бітного unsigned діапазону. + const masked = value & BIGINT_MASK_64; + + const lo = Number(masked & BIGINT_U32_MASK); + const hi = Number((masked >> BIGINT_32) & BIGINT_U32_MASK); + + const b = id + id; + const state = this._state; + state[b] = hi >>> 0; + state[b + 1] = lo >>> 0; + } + + /** + * Reads the value as a JavaScript Number (<= 2^53-1). + */ + readNumber(id: number): number { + const b = id + id; + const state = this._state; + + const hi = state[b]!; + const lo = state[b + 1]!; + + return hi * TWO_32 + lo; + } + + /** + * Writes a Number (accurate up to 2^53). + * High-performance when exact 64-bit precision is not required. + */ + writeNumber(id: number, value: number): void { + let v = +value; + const b = id + id; + const state = this._state; + + if (v <= 0) { + state[b] = 0; + state[b + 1] = 0; + return; + } + + if (v > Number.MAX_SAFE_INTEGER) { + v = Number.MAX_SAFE_INTEGER; + } + + const lo = v >>> 0; + const hi = Math.floor(v * TWO_NEG_32); + + state[b] = hi; + state[b + 1] = lo; + } + + /** + * Fast bulk copy from another Uint64Array. + */ + copyFrom( + source: Uint64Storage, + sourceStart = 0, + destStart = 0, + count?: number, + ): void { + const srcSize = source.size; + const start = sourceStart >>> 0; + const dst = destStart >>> 0; + + const actual = (count === undefined ? srcSize - start : count) >>> 0; + const endDest = dst + actual; + + if (endDest > this._capacity) { + while (endDest > this._capacity) this._grow(); + } + + const len = actual << 1; + const sb = start << 1; + const db = dst << 1; + + const srcBuf = source.toUint32Array(); + this._state.set(srcBuf.subarray(sb, sb + len), db); + + if (endDest > this._size) this._size = endDest; + } + + /** + * Fills a range of elements with the given `[hi, lo]` pair. + * Optimized to work on the underlying Uint32Array indices directly. + */ + fill(hi: number, lo: number, start = 0, end = this._size): void { + const h = hi >>> 0; + const l = lo >>> 0; + + const s = this._state; + let i = (start >>> 0) << 1; + const end2 = (end >>> 0) << 1; + + for (; i < end2; i += 2) { + s[i] = h; + s[i + 1] = l; + } + } + + /** + * Resets the logical size to zero. + * Underlying memory is preserved. + */ + clear(): void { + this._size = 0; + } + + /** + * Returns direct access to the underlying Uint32Array buffer. + */ + getBuffer(): U64InterleavedArray { + return this._state; + } + + /** + * Returns a Uint32Array view on a range of `[hi, lo]` pairs. + * No memory is copied. + */ + subarray(start: number, end = this._size): Uint32Array { + const s = start >>> 0; + const e = end >>> 0; + return this._state.subarray(s << 1, e << 1); + } + + /** + * Doubles the allocated capacity (like a vector). + */ + private _grow(): void { + const prevCap = this._capacity; + const nextCap = prevCap ? prevCap << 1 : 16; + + const next = new Uint32Array(nextCap << 1) as U64InterleavedArray; + next.set(this._state); + + this._capacity = nextCap; + this._state = next; + } + + /** Number of allocated elements. */ + get size(): number { + return this._size; + } + + /** Current capacity (in elements). */ + get capacity(): number { + return this._capacity; + } + + /** Memory usage in bytes. */ + get memoryUsage(): number { + return this._state.byteLength; + } +} diff --git a/packages/@reflex/core/tests/collections/invariant.test.ts b/packages/@reflex/core/tests/collections/invariant.test.ts new file mode 100644 index 0000000..6cb8dd1 --- /dev/null +++ b/packages/@reflex/core/tests/collections/invariant.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from "vitest"; +import { UnrolledQueue } from "../../src/collections/unrolled-queue"; + +describe("UnrolledQueue — structural invariants", () => { + it("node count remains valid under growth/shrink cycles", () => { + const q = new UnrolledQueue({ nodeSize: 4 }); + + for (let cycle = 0; cycle < 30; cycle++) { + for (let i = 0; i < 200; i++) q.enqueue(i); + for (let i = 0; i < 150; i++) q.dequeue(); + } + + // Длина не отрицательная + expect(q.length).toBeGreaterThanOrEqual(0); + + // estimateNodes >= реальное число + const est = q.estimateNodes(); + + // есть хотя бы 1 узел + expect(est).toBeGreaterThanOrEqual(1); + }); + + it("length always equals sum of segments", () => { + const q = new UnrolledQueue({ nodeSize: 8 }); + + for (let r = 0; r < 10; r++) { + for (let i = 0; i < 300; i++) q.enqueue(i); + for (let i = 0; i < 125; i++) q.dequeue(); + } + + const reconstructed: number[] = [...q]; + expect(reconstructed.length).toBe(q.length); + }); + + it("iterator always matches dequeue order", () => { + const q = new UnrolledQueue({ nodeSize: 16 }); + + for (let i = 0; i < 300; i++) q.enqueue(i); + + const fromIterator = [...q]; + const fromDequeue: number[] = []; + + while (q.length) { + fromDequeue.push(q.dequeue()!); + } + + expect(fromIterator).toEqual(fromDequeue); + }); + + it("survives heavy mixed operations", () => { + const q = new UnrolledQueue({ nodeSize: 8 }); + const mirror: number[] = []; + + for (let i = 0; i < 10000; i++) { + if (Math.random() > 0.55) { + q.enqueue(i); + mirror.push(i); + } else { + const a = q.dequeue(); + const b = mirror.shift(); + expect(a).toBe(b); + } + + expect(q.length).toBe(mirror.length); + } + }); +}); diff --git a/packages/@reflex/core/tests/collections/unrolled-queue.bench.ts b/packages/@reflex/core/tests/collections/unrolled-queue.bench.ts new file mode 100644 index 0000000..1b231f1 --- /dev/null +++ b/packages/@reflex/core/tests/collections/unrolled-queue.bench.ts @@ -0,0 +1,36 @@ +import { bench, describe } from "vitest"; +import { UnrolledQueue } from "../../src/collections/unrolled-queue"; + +describe("UnrolledQueue — Microbench", () => { + const N = 200_000; + + bench("enqueue N", () => { + const q = new UnrolledQueue({ nodeSize: 2048 }); + + for (let i = 0; i < N; i++) q.enqueue(i); + }); + + bench("enqueue + dequeue N", () => { + const q = new UnrolledQueue({ nodeSize: 2048 }); + + for (let i = 0; i < N; i++) q.enqueue(i); + for (let i = 0; i < N; i++) q.dequeue(); + }); + + bench("mixed workload (50/50)", () => { + const q = new UnrolledQueue({ nodeSize: 1024 }); + let x = 0; + + for (let i = 0; i < N; i++) { + if (i & 1) q.enqueue(x++); else q.dequeue(); + } + }); + + + bench("iterate over 100k", () => { + const q = new UnrolledQueue({ nodeSize: 1024 }); + + for (let i = 0; i < 100_000; i++) q.enqueue(i); + for (const _v of q) {} + }); +}); diff --git a/packages/@reflex/core/tests/collections/unrolled-queue.property.test.ts b/packages/@reflex/core/tests/collections/unrolled-queue.property.test.ts new file mode 100644 index 0000000..1ce715e --- /dev/null +++ b/packages/@reflex/core/tests/collections/unrolled-queue.property.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect } from "vitest" +import fc from "fast-check" +import { UnrolledQueue } from "../../src/collections/unrolled-queue" + +describe("UnrolledQueue — property based tests", () => { + it("preserves FIFO under random operations", () => { + fc.assert( + fc.property( + fc.array(fc.integer({ min: -10_000, max: 10_000 }), { + minLength: 1, + maxLength: 5000 + }), + (values) => { + const q = new UnrolledQueue({ nodeSize: 16 }) + const reference: number[] = [] + + for (const value of values) { + if (Math.random() > 0.35) { + q.enqueue(value) + reference.push(value) + } else { + const a = q.dequeue() + const b = reference.shift() + + expect(a).toBe(b) + } + + expect(q.length).toBe(reference.length) + } + + while (reference.length > 0) { + expect(q.dequeue()).toBe(reference.shift()) + } + + expect(q.dequeue()).toBe(undefined) + expect(q.peek()).toBe(null) + expect(q.length).toBe(0) + } + ), + { numRuns: 300 } + ) + }) + + it("correctly clears and reuses after arbitrary state", () => { + fc.assert( + fc.property( + fc.array(fc.integer(), { minLength: 1, maxLength: 2000 }), + (values) => { + const q = new UnrolledQueue({ nodeSize: 8 }) + + for (const v of values) q.enqueue(v) + q.clear() + + expect(q.length).toBe(0) + expect(q.peek()).toBe(null) + expect(q.dequeue()).toBe(undefined) + + for (let i = 0; i < 100; i++) q.enqueue(i * 2) + + for (let i = 0; i < 100; i++) { + expect(q.dequeue()).toBe(i * 2) + } + + expect(q.length).toBe(0) + } + ) + ) + }) + + it("estimateNodes always over/near estimates", () => { + fc.assert( + fc.property( + fc.integer({ min: 1, max: 2000 }), + (count) => { + const q = new UnrolledQueue({ nodeSize: 8 }) + const maxPerNode = 7 + + for (let i = 0; i < count; i++) q.enqueue(i) + + const est = q.estimateNodes() + const realMin = Math.ceil(count / maxPerNode) + + expect(est).toBeGreaterThanOrEqual(realMin) + expect(est).toBeLessThanOrEqual(realMin + 2) + } + ), + { numRuns: 250 } + ) + }) +}) diff --git a/packages/@reflex/core/tests/collections/unrolled-queue.stress.bench.ts b/packages/@reflex/core/tests/collections/unrolled-queue.stress.bench.ts new file mode 100644 index 0000000..8a00b45 --- /dev/null +++ b/packages/@reflex/core/tests/collections/unrolled-queue.stress.bench.ts @@ -0,0 +1,108 @@ +/** + * Unrolled-Linked Queue implementation + * + * Inspired by Node.js internal FixedQueue but enhanced: + * - Uses a linked list of fixed-size circular buffer nodes (unrolled queue) instead of one static ring. + * - On enqueue: if current head node is full → allocate (or reuse from pool) a new node and link it. + * - On dequeue: if current tail node is emptied and has next → detach it and return it to pool. + * - Node pooling: detached nodes up to POOL_MAX are kept and reused to reduce GC churn. + * - Circular buffer inside each node: size is power of two, readIndex/writeIndex wrap via bit-mask for speed. + * - Iterable: supports iteration from tail → head, enabling full traversal. + * - Clear/reset support: can recycle all nodes and re-initialize. + * - Time complexity: amortised O(1) for enqueue/dequeue; memory footprint adapts dynamically. + * + * Typical use cases: + * - High-throughput runtime/event queues. + * - Scenarios where GC pressure must be minimised. + * - Systems demanding predictable, low-latency enqueue/dequeue operations. + * + * Note: For maximum performance, pick nodeSize as power of two (e.g., 1024, 2048). + * + */ + +import { bench, describe } from "vitest"; +import { performance } from "node:perf_hooks"; +import { UnrolledQueue } from "../../src/collections/unrolled-queue"; + +interface BenchOptions { + ops: number; + rounds: number; + warmup: number; + nodeSize: number; + poolSize?: number; +} + +function memoryUsageMB() { + return process.memoryUsage().heapUsed / 1024 / 1024; +} + +function runSingleRound(QueueCtor: typeof UnrolledQueue, opts: BenchOptions) { + const q = new QueueCtor({ nodeSize: opts.nodeSize }); + + // Перед измерением — сброс мусора + if (global.gc) global.gc(); + + const memStart = memoryUsageMB(); + const t0 = performance.now(); + + let prevent = 0; + + for (let i = 0; i < opts.ops; i++) { + q.enqueue({ id: i }); + } + + for (let i = 0; i < opts.ops; i++) { + const item = q.dequeue(); + if (item) prevent += (item as any).id; + } + + const t1 = performance.now(); + const memEnd = memoryUsageMB(); + + if (prevent === 0) console.log("prevent"); + + return { + cpu: t1 - t0, + ram: memEnd - memStart, + }; +} + +function runAveraged(QueueCtor: typeof UnrolledQueue, opts: BenchOptions) { + const warmup = opts.warmup; + const rounds = opts.rounds; + + let cpu = 0; + let ram = 0; + + // Warm-up + real rounds + for (let i = 0; i < warmup + rounds; i++) { + const { cpu: c, ram: r } = runSingleRound(QueueCtor, opts); + + if (i >= warmup) { + cpu += c; + ram += r; + } + } + + return { + cpu: +(cpu / rounds).toFixed(3), + ram: +(ram / rounds).toFixed(3), + }; +} + +describe("UnrolledQueue — Stress Benchmark (CPU + RAM)", () => { + const opts: BenchOptions = { + ops: 200_000, + rounds: 5, + warmup: 2, + nodeSize: 2048, + }; + + bench(`stress: enqueue+dequeue ${opts.ops} ops`, () => { + const res = runAveraged(UnrolledQueue, opts); + console.log( + `\nStress results — ${opts.ops} ops:\n`, + `CPU(ms): ${res.cpu}\nRAM(MB): ${res.ram}\n`, + ); + }); +}); diff --git a/packages/@reflex/core/tests/graph/graph.bench.ts b/packages/@reflex/core/tests/graph/graph.bench.ts new file mode 100644 index 0000000..1d7033a --- /dev/null +++ b/packages/@reflex/core/tests/graph/graph.bench.ts @@ -0,0 +1,165 @@ +import { describe, bench } from "vitest"; + +import { + linkSourceToObserverUnsafe, + unlinkSourceFromObserverUnsafe, + unlinkAllObserversUnsafe, +} from "../../src/graph/process/graph.methods"; + +import { GraphNode } from "../../src/graph/process/graph.node"; +import { GraphService } from "../../src/graph/graph.contract"; + +const r = new GraphService(); + +/** Create node */ +function makeNode(): GraphNode { + return new GraphNode(0); +} + +/** Collect OUT edges of a node (edges: node → observer) */ +function collectOutEdges(node: GraphNode) { + const arr = []; + let e = node.firstOut; + while (e) { + arr.push(e); + e = e.nextOut; + } + return arr; +} + +/** Collect IN edges of a node (edges: source → node) */ +function collectInEdges(node: GraphNode) { + const arr = []; + let e = node.firstIn; + while (e) { + arr.push(e); + e = e.nextIn; + } + return arr; +} + +describe("DAG O(1) intrusive graph benchmarks (edge-based)", () => { + // ────────────────────────────────────────────────────────────── + // 1. Basic 1k link/unlink cycles for both APIs + // ────────────────────────────────────────────────────────────── + + bench("GraphService.addObserver/removeObserver (1k ops)", () => { + const A = makeNode(); + const B = makeNode(); + + for (let i = 0; i < 1000; i++) { + r.addObserver(A, B); + r.removeObserver(A, B); + } + }); + + bench("Unsafe link/unlink (1k ops)", () => { + const A = makeNode(); + const B = makeNode(); + + for (let i = 0; i < 1000; i++) { + linkSourceToObserverUnsafe(A, B); + unlinkSourceFromObserverUnsafe(A, B); + } + }); + + // ────────────────────────────────────────────────────────────── + // 2. Mixed random link/unlink operations + // ────────────────────────────────────────────────────────────── + + bench("1000 mixed link/unlink operations (random-ish)", () => { + const nodes = Array.from({ length: 50 }, makeNode); + + for (let i = 0; i < 1000; i++) { + const a = nodes[(i * 5) % nodes.length]!; + const b = nodes[(i * 17) % nodes.length]!; + + if (a !== b) { + r.addObserver(a, b); + if (i % 2 === 0) r.removeObserver(a, b); + } + } + }); + + // ────────────────────────────────────────────────────────────── + // 3. Star linking + // ────────────────────────────────────────────────────────────── + + bench("massive star graph: 1 source → 1k observers", () => { + const source = makeNode(); + const observers = Array.from({ length: 1000 }, makeNode); + + for (const obs of observers) r.addObserver(source, obs); + }); + + // ────────────────────────────────────────────────────────────── + // 4. Star unlink (bulk) + // ────────────────────────────────────────────────────────────── + + bench("massive star unlink: unlink all observers from 1 source (1k)", () => { + const source = makeNode(); + const observers = Array.from({ length: 1000 }, makeNode); + + for (const obs of observers) r.addObserver(source, obs); + unlinkAllObserversUnsafe(source); + }); + + // ────────────────────────────────────────────────────────────── + // 5. Star unlink piecewise (corrected) + // ────────────────────────────────────────────────────────────── + + bench("star unlink piecemeal: remove each observer individually", () => { + const source = makeNode(); + const observers = Array.from({ length: 1000 }, makeNode); + + for (const obs of observers) r.addObserver(source, obs); + + // Correct: removeObserver, not addObserver + for (const obs of observers) r.removeObserver(source, obs); + }); + + // ────────────────────────────────────────────────────────────── + // 7. Random DAG simulation (10k edges) + // ────────────────────────────────────────────────────────────── + + bench("DAG simulation: 100 nodes, 10k random edges", () => { + const nodes = Array.from({ length: 100 }, makeNode); + + for (let i = 0; i < 10000; i++) { + const a = nodes[Math.floor(Math.random() * 100)]!; + const b = nodes[Math.floor(Math.random() * 100)]!; + if (a !== b) linkSourceToObserverUnsafe(a, b); + } + }); + + // ────────────────────────────────────────────────────────────── + // 8. Degree counting sanity test + // ────────────────────────────────────────────────────────────── + + bench("counting observer/source degree: 1k nodes, sparse connections", () => { + const nodes = Array.from({ length: 1000 }, makeNode); + + // Sparse layering: DAG i → (i+1..i+4) + for (let i = 0; i < 1000; i++) { + const src = nodes[i]!; + for (let j = i + 1; j < Math.min(i + 5, nodes.length); j++) { + r.addObserver(src, nodes[j]!); + } + } + + let sumOut = 0; + let sumIn = 0; + + for (const n of nodes) { + sumOut += n.outCount; + sumIn += n.inCount; + } + + if (sumOut !== sumIn) { + throw new Error( + `Degree mismatch: OUT=${sumOut}, IN=${sumIn} — graph invariant broken`, + ); + } + }); + +}); diff --git a/packages/@reflex/core/tests/graph/graph.test.ts b/packages/@reflex/core/tests/graph/graph.test.ts new file mode 100644 index 0000000..3ebcb30 --- /dev/null +++ b/packages/@reflex/core/tests/graph/graph.test.ts @@ -0,0 +1,184 @@ +import { describe, it, expect } from "vitest"; +import { + linkSourceToObserverUnsafe, + unlinkSourceFromObserverUnsafe, + unlinkAllObserversUnsafe, + unlinkAllSourcesUnsafe, +} from "../../src/graph/process/graph.methods"; +import { GraphNode, GraphEdge } from "../../src/graph/process/graph.node"; + +// helpers +function collectOutEdges(node: GraphNode): GraphEdge[] { + const result: GraphEdge[] = []; + let cur = node.firstOut; + while (cur) { + result.push(cur); + cur = cur.nextOut; + } + return result; +} + +function collectInEdges(node: GraphNode): GraphEdge[] { + const result: GraphEdge[] = []; + let cur = node.firstIn; + while (cur) { + result.push(cur); + cur = cur.nextIn; + } + return result; +} + +describe("Edge-based Intrusive Graph", () => { + it("creates symmetric edge between source and observer", () => { + const source = new GraphNode(0); + const observer = new GraphNode(0); + + const e = linkSourceToObserverUnsafe(source, observer); + + // OUT adjacency + expect(source.firstOut).toBe(e); + expect(source.lastOut).toBe(e); + expect(source.outCount).toBe(1); + + // IN adjacency + expect(observer.firstIn).toBe(e); + expect(observer.lastIn).toBe(e); + expect(observer.inCount).toBe(1); + + // symmetry + expect(e.from).toBe(source); + expect(e.to).toBe(observer); + }); + + it("supports multiple observers for one source", () => { + const source = new GraphNode(0); + const o1 = new GraphNode(0); + const o2 = new GraphNode(0); + const o3 = new GraphNode(0); + + const e1 = linkSourceToObserverUnsafe(source, o1); + const e2 = linkSourceToObserverUnsafe(source, o2); + const e3 = linkSourceToObserverUnsafe(source, o3); + + const chain = collectOutEdges(source); + + expect(chain.length).toBe(3); + expect(chain[0]).toBe(e1); + expect(chain[1]).toBe(e2); + expect(chain[2]).toBe(e3); + + expect(chain[0].nextOut).toBe(chain[1]); + expect(chain[1].nextOut).toBe(chain[2]); + expect(chain[2].nextOut).toBe(null); + + expect(chain[1].prevOut).toBe(chain[0]); + expect(chain[2].prevOut).toBe(chain[1]); + }); + + it("supports multiple sources for one observer", () => { + const observer = new GraphNode(0); + const s1 = new GraphNode(0); + const s2 = new GraphNode(0); + const s3 = new GraphNode(0); + + const e1 = linkSourceToObserverUnsafe(s1, observer); + const e2 = linkSourceToObserverUnsafe(s2, observer); + const e3 = linkSourceToObserverUnsafe(s3, observer); + + const chain = collectInEdges(observer); + + expect(chain.length).toBe(3); + expect(chain[0]).toBe(e1); + expect(chain[1]).toBe(e2); + expect(chain[2]).toBe(e3); + + expect(chain[0].nextIn).toBe(chain[1]); + expect(chain[1].nextIn).toBe(chain[2]); + expect(chain[2].nextIn).toBe(null); + }); + + it("unlinkSourceFromObserverUnsafe removes only matching edge", () => { + const observer = new GraphNode(0); + const source = new GraphNode(0); + + linkSourceToObserverUnsafe(source, observer); + + expect(observer.inCount).toBe(1); + + unlinkSourceFromObserverUnsafe(source, observer); + + expect(observer.inCount).toBe(0); + expect(observer.firstIn).toBeNull(); + expect(observer.lastIn).toBeNull(); + + expect(source.firstOut).toBeNull(); + expect(source.lastOut).toBeNull(); + expect(source.outCount).toBe(0); + }); + + it("unlinkSourceFromObserverUnsafe removes middle of out-list", () => { + const observer = new GraphNode(0); + const s1 = new GraphNode(0); + const s2 = new GraphNode(0); + const s3 = new GraphNode(0); + + linkSourceToObserverUnsafe(s1, observer); + linkSourceToObserverUnsafe(s2, observer); + linkSourceToObserverUnsafe(s3, observer); + + unlinkSourceFromObserverUnsafe(s2, observer); + + const chain = collectInEdges(observer); + + expect(chain.length).toBe(2); + expect(chain[0].from).toBe(s1); + expect(chain[1].from).toBe(s3); + }); + + it("unlinkAllObserversUnsafe clears all out-edges", () => { + const source = new GraphNode(0); + const o1 = new GraphNode(0); + const o2 = new GraphNode(0); + const o3 = new GraphNode(0); + + linkSourceToObserverUnsafe(source, o1); + linkSourceToObserverUnsafe(source, o2); + linkSourceToObserverUnsafe(source, o3); + + expect(source.outCount).toBe(3); + + unlinkAllObserversUnsafe(source); + + expect(source.outCount).toBe(0); + expect(source.firstOut).toBeNull(); + expect(source.lastOut).toBeNull(); + + // every observer has no incoming edges now + expect(o1.firstIn).toBeNull(); + expect(o2.firstIn).toBeNull(); + expect(o3.firstIn).toBeNull(); + }); + + it("unlinkAllSourcesUnsafe clears all in-edges", () => { + const observer = new GraphNode(0); + const s1 = new GraphNode(0); + const s2 = new GraphNode(0); + const s3 = new GraphNode(0); + + linkSourceToObserverUnsafe(s1, observer); + linkSourceToObserverUnsafe(s2, observer); + linkSourceToObserverUnsafe(s3, observer); + + expect(observer.inCount).toBe(3); + + unlinkAllSourcesUnsafe(observer); + + expect(observer.inCount).toBe(0); + expect(observer.firstIn).toBeNull(); + expect(observer.lastIn).toBeNull(); + + expect(s1.firstOut).toBeNull(); + expect(s2.firstOut).toBeNull(); + expect(s3.firstOut).toBeNull(); + }); +}); diff --git a/packages/@reflex/core/tests/ownership/ownerhip.test.ts b/packages/@reflex/core/tests/ownership/ownerhip.test.ts new file mode 100644 index 0000000..2000425 --- /dev/null +++ b/packages/@reflex/core/tests/ownership/ownerhip.test.ts @@ -0,0 +1,633 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { OwnershipService } from "../../src/ownership/ownership.node"; +import { + createOwnershipScope, + OwnershipScope, +} from "../../src/ownership/ownership.scope"; +import type { OwnershipNode } from "../../src/ownership/ownership.node"; + +/* ────────────────────────────────────────────────────────────── + * Test helpers (no `any`) + * ────────────────────────────────────────────────────────────── */ + +function collectChildren(parent: OwnershipNode): OwnershipNode[] { + const out: OwnershipNode[] = []; + let c = parent._firstChild; + while (c !== null) { + out.push(c); + c = c._nextSibling; + } + return out; +} + +function assertSiblingChain(parent: OwnershipNode): void { + const kids = collectChildren(parent); + + // parent pointers + for (const k of kids) { + expect(k._parent).toBe(parent); + } + + // first/last links + if (kids.length === 0) { + expect(parent._firstChild).toBeNull(); + expect(parent._lastChild).toBeNull(); + } else { + expect(parent._firstChild).toBe(kids[0]); + expect(parent._lastChild).toBe(kids[kids.length - 1]); + } + + // forward/backward consistency + for (let i = 0; i < kids.length; i++) { + const cur = kids[i]!; + const prev = i === 0 ? null : kids[i - 1]!; + const next = i === kids.length - 1 ? null : kids[i + 1]!; + + expect(cur._prevSibling).toBe(prev); + expect(cur._nextSibling).toBe(next); + + if (prev !== null) expect(prev._nextSibling).toBe(cur); + if (next !== null) expect(next._prevSibling).toBe(cur); + } + + // count accuracy (white-box but meaningful) + expect(parent._childCount).toBe(kids.length); +} + +function assertDetached(node: OwnershipNode): void { + expect(node._parent).toBeNull(); + expect(node._prevSibling).toBeNull(); + expect(node._nextSibling).toBeNull(); +} + +/** + * Security-sensitive keys for prototype-pollution checks. + * If your context layer is a plain object / proto-chain, these must be blocked. + */ +const PROTO_KEYS: Array = ["__proto__", "prototype", "constructor"]; + +/* ────────────────────────────────────────────────────────────── + * Ownership Safety Spec — Tests + * ────────────────────────────────────────────────────────────── */ + +describe("Ownership Safety Spec (I–VIII)", () => { + let service: OwnershipService; + + beforeEach(() => { + service = new OwnershipService(); + }); + + /*───────────────────────────────────────────────* + * I. Structural Invariants + *───────────────────────────────────────────────*/ + describe("I. Structural Invariants", () => { + it("I1 Single Parent: child cannot have two parents after reparent", () => { + const p1 = service.createOwner(null); + const p2 = service.createOwner(null); + const c = service.createOwner(null); + + service.appendChild(p1, c); + service.appendChild(p2, c); + + // child parent updated + expect(c._parent).toBe(p2); + + // p1 should no longer reference child + expect(collectChildren(p1)).not.toContain(c); + expect(p1._childCount).toBe(0); + + // p2 contains child exactly once + const kids2 = collectChildren(p2); + expect(kids2).toContain(c); + expect(kids2.filter((x) => x === c).length).toBe(1); + + assertSiblingChain(p1); + assertSiblingChain(p2); + }); + + it("I2 Sibling Chain Consistency: multi-append preserves order and links", () => { + const p = service.createOwner(null); + const a = service.createOwner(null); + const b = service.createOwner(null); + const c = service.createOwner(null); + + service.appendChild(p, a); + service.appendChild(p, b); + service.appendChild(p, c); + + assertSiblingChain(p); + expect(collectChildren(p)).toEqual([a, b, c]); + }); + + it("I3 Child Count Accuracy: _childCount matches traversal", () => { + const p = service.createOwner(null); + for (let i = 0; i < 50; i++) service.createOwner(p); + + assertSiblingChain(p); + + // remove some + const kids = collectChildren(p); + for (let i = 0; i < kids.length; i += 3) { + service.removeChild(p, kids[i]!); + } + + assertSiblingChain(p); + }); + + it("I4 Safe Reparenting: reparent preserves integrity of both lists", () => { + const p1 = service.createOwner(null); + const p2 = service.createOwner(null); + + const kids: OwnershipNode[] = []; + for (let i = 0; i < 10; i++) kids.push(service.createOwner(p1)); + + // move middle one + const mid = kids[5]!; + service.appendChild(p2, mid); + + assertSiblingChain(p1); + assertSiblingChain(p2); + + expect(collectChildren(p2)).toEqual([mid]); + expect(collectChildren(p1)).not.toContain(mid); + }); + + it("I5 Orphan Removal: removeChild detaches child refs", () => { + const p = service.createOwner(null); + const c = service.createOwner(null); + + service.appendChild(p, c); + service.removeChild(p, c); + + assertDetached(c); + assertSiblingChain(p); + }); + + it("Removal is safe when child is not owned by parent (no throw, no mutation)", () => { + const p = service.createOwner(null); + const other = service.createOwner(null); + const c = service.createOwner(other); + + // should not throw and should not detach from real parent + expect(() => service.removeChild(p, c)).not.toThrow(); + expect(c._parent).toBe(other); + + assertSiblingChain(p); + assertSiblingChain(other); + }); + }); + + /*───────────────────────────────────────────────* + * II. Context Invariants + *───────────────────────────────────────────────*/ + describe("II. Context Invariants", () => { + it("II1 Lazy Context Initialization: _context stays null until first access/provide", () => { + const o = service.createOwner(null); + expect(o._context).toBeNull(); + + // getContext should initialize + const ctx = service.getContext(o); + expect(ctx).toBeDefined(); + expect(o._context).not.toBeNull(); + expect(service.getContext(o)).toBe(ctx); + }); + + it("II2 Inheritance Without Mutation: child can read parent, overrides are isolated", () => { + const parent = service.createOwner(null); + service.provide(parent, "shared", 1); + + const c1 = service.createOwner(parent); + const c2 = service.createOwner(parent); + + expect(service.inject(c1, "shared")).toBe(1); + expect(service.inject(c2, "shared")).toBe(1); + + service.provide(c1, "shared", 10); + service.provide(c2, "shared", 20); + + expect(service.inject(parent, "shared")).toBe(1); + expect(service.inject(c1, "shared")).toBe(10); + expect(service.inject(c2, "shared")).toBe(20); + }); + + it("II3 Forbidden Prototype Keys: providing __proto__/constructor/prototype must be rejected", () => { + const o = service.createOwner(null); + + // These tests are intentionally strict. If they fail now, it's a real vulnerability to fix. + for (const key of PROTO_KEYS) { + expect(() => + service.provide(o, key as unknown as any, { hacked: true }), + ).toThrow(); + } + }); + + it("II4 Self Reference Prevention: cannot provide owner itself as a value", () => { + const o = service.createOwner(null); + + // Strict: if current code does not throw yet, you should add the guard in contextProvide/provide + expect(() => service.provide(o, "self", o)).toThrow(); + }); + + it("hasOwn vs inject: distinguishes own vs inherited keys", () => { + const parent = service.createOwner(null); + service.provide(parent, "inherited", 1); + + const child = service.createOwner(parent); + service.provide(child, "own", 2); + + expect(service.hasOwn(child, "own")).toBe(true); + expect(service.hasOwn(child, "inherited")).toBe(false); + + expect(service.inject(child, "inherited")).toBe(1); + expect(service.inject(child, "own")).toBe(2); + }); + + it("supports symbol keys (context keys)", () => { + const o = service.createOwner(null); + const k = Symbol("k") as unknown as any; + + service.provide(o, k, 123); + expect(service.inject(o, k)).toBe(123); + expect(service.hasOwn(o, k)).toBe(true); + }); + + it("returns undefined for missing keys", () => { + const o = service.createOwner(null); + expect(service.inject(o, "missing")).toBeUndefined(); + expect(service.hasOwn(o, "missing")).toBe(false); + }); + + it("allows null/undefined values without breaking own-ness", () => { + const o = service.createOwner(null); + service.provide(o, "null", null); + service.provide(o, "undef", undefined); + + expect(service.inject(o, "null")).toBeNull(); + expect(service.inject(o, "undef")).toBeUndefined(); + expect(service.hasOwn(o, "null")).toBe(true); + expect(service.hasOwn(o, "undef")).toBe(true); + }); + }); + + /*───────────────────────────────────────────────* + * III. Cleanup Invariants + *───────────────────────────────────────────────*/ + describe("III. Cleanup Invariants", () => { + it("III1 Lazy Cleanups: _cleanups is null until first registration", () => { + const o = service.createOwner(null); + expect(o._cleanups).toBeNull(); + + service.onScopeCleanup(o, () => {}); + expect(o._cleanups).not.toBeNull(); + expect(Array.isArray(o._cleanups)).toBe(true); + }); + + it("III2 Order Guarantee (LIFO): cleanups run in reverse registration order", () => { + const o = service.createOwner(null); + const order: number[] = []; + + service.onScopeCleanup(o, () => order.push(1)); + service.onScopeCleanup(o, () => order.push(2)); + service.onScopeCleanup(o, () => order.push(3)); + + service.dispose(o); + expect(order).toEqual([3, 2, 1]); + }); + + it("III3 Idempotent Dispose: cleanups execute exactly once", () => { + const o = service.createOwner(null); + const spy = vi.fn(); + + service.onScopeCleanup(o, spy); + service.dispose(o); + service.dispose(o); + service.dispose(o); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it("III4 Continue on Error: cleanup errors do not prevent others", () => { + const o = service.createOwner(null); + const spy1 = vi.fn(); + const spy2 = vi.fn(() => { + throw new Error("cleanup"); + }); + const spy3 = vi.fn(); + + const consoleError = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + service.onScopeCleanup(o, spy1); + service.onScopeCleanup(o, spy2); + service.onScopeCleanup(o, spy3); + + expect(() => service.dispose(o)).not.toThrow(); + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(1); + expect(spy3).toHaveBeenCalledTimes(1); + expect(consoleError).toHaveBeenCalled(); + + consoleError.mockRestore(); + }); + }); + + /*───────────────────────────────────────────────* + * IV. Disposal Order & Tree Safety + *───────────────────────────────────────────────*/ + describe("IV. Disposal Order & Tree Safety", () => { + it("IV1 Post-order traversal: children dispose before parents (via cleanup order)", () => { + const root = service.createOwner(null); + const c1 = service.createOwner(root); + const c2 = service.createOwner(root); + const g = service.createOwner(c1); + + const order: string[] = []; + + service.onScopeCleanup(g, () => order.push("grandchild")); + service.onScopeCleanup(c1, () => order.push("child1")); + service.onScopeCleanup(c2, () => order.push("child2")); + service.onScopeCleanup(root, () => order.push("root")); + + service.dispose(root); + + expect(order.indexOf("grandchild")).toBeLessThan(order.indexOf("child1")); + expect(order.indexOf("child1")).toBeLessThan(order.indexOf("root")); + expect(order.indexOf("child2")).toBeLessThan(order.indexOf("root")); + }); + + it("IV2 Skip already disposed nodes: disposing subtree then root is safe and does not double-run", () => { + const root = service.createOwner(null); + const c1 = service.createOwner(root); + const c2 = service.createOwner(root); + + const spy1 = vi.fn(); + const spy2 = vi.fn(); + + service.onScopeCleanup(c1, spy1); + service.onScopeCleanup(c2, spy2); + + service.dispose(c1); + service.dispose(root); + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(1); + }); + + it("IV3 Full structural cleanup: after dispose, node has no links/context/cleanups", () => { + const root = service.createOwner(null); + const child = service.createOwner(root); + + service.provide(root, "x", 1); + service.onScopeCleanup(root, () => {}); + service.onScopeCleanup(child, () => {}); + + service.dispose(root); + + // root cleared + expect(root._parent).toBeNull(); + expect(root._firstChild).toBeNull(); + expect(root._lastChild).toBeNull(); + expect(root._context).toBeNull(); + expect(root._cleanups).toBeNull(); + expect(root._childCount).toBe(0); + + // child cleared + expect(child._parent).toBeNull(); + expect(child._firstChild).toBeNull(); + expect(child._lastChild).toBeNull(); + expect(child._context).toBeNull(); + expect(child._cleanups).toBeNull(); + expect(child._childCount).toBe(0); + }); + }); + + /*───────────────────────────────────────────────* + * V. OwnershipState Invariants + *───────────────────────────────────────────────*/ + describe("V. OwnershipState Invariants", () => { + it("V1 Mutations after dispose are rejected or ignored safely (no corruption)", () => { + const root = service.createOwner(null); + const child = service.createOwner(null); + + service.dispose(root); + + // append on disposed root should not attach + expect(() => service.appendChild(root, child)).not.toThrow(); + expect(child._parent).toBeNull(); + + // cleanup registration on disposed node: should not register / or should throw; choose your policy + // Current code ignores silently; test for safety (no crash, no reanimation) + expect(() => service.onScopeCleanup(root, () => {})).not.toThrow(); + + // provide on disposed node: policy-dependent. Safety requirement: no throw OR throw, but no corruption. + expect(() => service.provide(root, "k", 1)).not.toThrow(); + expect(root._parent).toBeNull(); + expect(root._firstChild).toBeNull(); + }); + + it("V1 removeChild on disposed parent is safe and does not detach unrelated nodes", () => { + const p = service.createOwner(null); + const c = service.createOwner(p); + + service.dispose(p); + + // should not detach child from p because p already disposed (but both are disposed anyway) + expect(() => service.removeChild(p, c)).not.toThrow(); + expect(c._parent).toBeNull(); + }); + }); + + /*───────────────────────────────────────────────* + * VI. Scope Safety + *───────────────────────────────────────────────*/ + describe("VI. Scope Safety", () => { + let scope: OwnershipScope; + + beforeEach(() => { + scope = createOwnershipScope(service); + }); + + afterEach(() => { + // no leaks: after each test, scope must be reset + expect(scope.getOwner()).toBeNull(); + }); + + it("VI1 Scope Isolation: withOwner restores even if callback throws", () => { + const o = service.createOwner(null); + + expect(() => { + scope.withOwner(o, () => { + throw new Error("boom"); + }); + }).toThrow("boom"); + + expect(scope.getOwner()).toBeNull(); + }); + + it("VI2 Nested Scope Restore: inner restores to outer, then to null", () => { + const outer = service.createOwner(null); + const inner = service.createOwner(null); + + scope.withOwner(outer, () => { + expect(scope.getOwner()).toBe(outer); + + scope.withOwner(inner, () => { + expect(scope.getOwner()).toBe(inner); + }); + + expect(scope.getOwner()).toBe(outer); + }); + + expect(scope.getOwner()).toBeNull(); + }); + + it("VI3 createScope Consistency: parent defaults to current owner", () => { + const parent = service.createOwner(null); + let created: OwnershipNode | null = null; + + scope.withOwner(parent, () => { + scope.createScope(() => { + created = scope.getOwner(); + }); + }); + + expect(created).not.toBeNull(); + expect(created!._parent).toBe(parent); + expect(scope.getOwner()).toBeNull(); + }); + + it("createScope works without current owner (creates root owner)", () => { + let root: OwnershipNode | null = null; + + scope.createScope(() => { + root = scope.getOwner(); + }); + + expect(root).not.toBeNull(); + expect(root!._parent).toBeNull(); + expect(scope.getOwner()).toBeNull(); + }); + + it("createScope restores even if callback throws", () => { + const parent = service.createOwner(null); + + expect(() => { + scope.withOwner(parent, () => { + scope.createScope(() => { + throw new Error("scope error"); + }); + }); + }).toThrow("scope error"); + + expect(scope.getOwner()).toBeNull(); + }); + }); + + /*───────────────────────────────────────────────* + * VII. Context Safety + *───────────────────────────────────────────────*/ + describe("VII. Context Safety", () => { + it("VII1 hasOwn vs inject: hasOwn only for local keys; inject follows chain", () => { + const p = service.createOwner(null); + const c = service.createOwner(p); + + service.provide(p, "k", 1); + + expect(service.hasOwn(c, "k")).toBe(false); + expect(service.inject(c, "k")).toBe(1); + + service.provide(c, "k", 2); + expect(service.hasOwn(c, "k")).toBe(true); + expect(service.inject(c, "k")).toBe(2); + expect(service.inject(p, "k")).toBe(1); + }); + + it("Context chain remains readable after structural mutations", () => { + const p1 = service.createOwner(null); + const p2 = service.createOwner(null); + const c = service.createOwner(p1); + + service.provide(p1, "x", 1); + expect(service.inject(c, "x")).toBe(1); + + // reparent + service.appendChild(p2, c); + + // After reparent: c should no longer inherit p1 context + // This expectation is a *design choice*. If you want inherited context to follow parent after reparent, + // it should be true; if you freeze context at creation-time, it should remain 1. + // + // Current implementation: getContext uses parent._context at creation time only, so behavior depends on when context is initialized. + // We set a strict security invariant here: reparent should not allow reading old parent chain unintentionally. + expect(service.inject(c, "x")).toBeUndefined(); + }); + }); + + /*───────────────────────────────────────────────* + * VIII. Error Strategy + *───────────────────────────────────────────────*/ + describe("VIII. Error Strategy", () => { + it("dispose is resilient: cleanup errors do not break disposal safety", () => { + const root = service.createOwner(null); + const child = service.createOwner(root); + + service.onScopeCleanup(child, () => { + throw new Error("child cleanup"); + }); + service.onScopeCleanup(root, () => {}); + + const consoleError = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + expect(() => service.dispose(root)).not.toThrow(); + consoleError.mockRestore(); + + // Safety post-condition: structure cleared + expect(root._firstChild).toBeNull(); + expect(root._lastChild).toBeNull(); + expect(child._parent).toBeNull(); + }); + + it("optional: fuzz mini-run should not corrupt invariants (structural)", () => { + const root = service.createOwner(null); + const pool: OwnershipNode[] = [root]; + + // small deterministic pseudo-fuzz + for (let i = 0; i < 200; i++) { + const r = i % 7; + + if (r === 0) { + // add child to random parent + const parent = pool[i % pool.length]!; + const n = service.createOwner(parent); + pool.push(n); + } else if (r === 1 && pool.length > 2) { + // remove a leaf-ish node if possible + const n = pool[pool.length - 1]!; + const p = n._parent; + if (p !== null) service.removeChild(p, n); + } else if (r === 2 && pool.length > 2) { + // reparent last node under root + const n = pool[pool.length - 1]!; + if (n !== root) service.appendChild(root, n); + } else if (r === 3) { + // context provide/read on random node + const n = pool[i % pool.length]!; + service.provide(n, "k", i); + service.inject(n, "k"); + } else { + // no-op + } + + // invariant check only for root chain + assertSiblingChain(root); + } + + service.dispose(root); + expect(root._firstChild).toBeNull(); + expect(root._lastChild).toBeNull(); + }); + }); +}); diff --git a/packages/@reflex/core/tests/ownership/ownership.bench.ts b/packages/@reflex/core/tests/ownership/ownership.bench.ts new file mode 100644 index 0000000..27cd76b --- /dev/null +++ b/packages/@reflex/core/tests/ownership/ownership.bench.ts @@ -0,0 +1,158 @@ +import { bench, describe } from "vitest"; +import { OwnershipService } from "../../src/ownership/ownership.node"; +import type { OwnershipNode } from "../../src/ownership/ownership.node"; + +const service = new OwnershipService(); + +/** + * Ownership System Microbenchmarks + * + * Measures hot-path ownership operations: + * - creation + * - structural mutations + * - cleanup execution + * - context propagation + */ + +describe("Ownership — Microbench", () => { + bench("create 100 children and dispose", () => { + const root = service.createOwner(null); + for (let i = 0; i < 100; i++) { + service.createOwner(root); + } + service.dispose(root); + }); + + bench("register 100 cleanups", () => { + const owner = service.createOwner(null); + for (let i = 0; i < 100; i++) { + service.onScopeCleanup(owner, () => {}); + } + service.dispose(owner); + }); + + bench("register 10k cleanups and dispose", () => { + const owner = service.createOwner(null); + for (let i = 0; i < 10_000; i++) { + service.onScopeCleanup(owner, () => {}); + } + service.dispose(owner); + }); + + bench("build balanced tree (depth 6 × width 3)", () => { + const root = service.createOwner(null); + let layer: OwnershipNode[] = [root]; + + for (let d = 0; d < 6; d++) { + const next: OwnershipNode[] = []; + for (const parent of layer) { + for (let i = 0; i < 3; i++) { + next.push(service.createOwner(parent)); + } + } + layer = next; + } + + service.dispose(root); + }); + + bench("build wide tree (3000 siblings)", () => { + const root = service.createOwner(null); + for (let i = 0; i < 3000; i++) { + service.createOwner(root); + } + service.dispose(root); + }); + + bench("build linear chain (depth 10k)", () => { + let node = service.createOwner(null); + const root = node; + + for (let i = 0; i < 10_000; i++) { + node = service.createOwner(node); + } + + service.dispose(root); + }); + + bench("context propagation (1000 depth, 100 reads)", () => { + let node = service.createOwner(null); + const root = node; + + for (let i = 0; i < 1000; i++) { + node = service.createOwner(node); + } + + service.provide(node, "value", 42); + + for (let i = 0; i < 100; i++) { + service.inject(node, "value"); + } + + service.dispose(root); + }); + + bench("context override isolation (100 children)", () => { + const root = service.createOwner(null); + service.provide(root, "key", 0); + + for (let i = 0; i < 100; i++) { + const child = service.createOwner(root); + service.provide(child, "key", i); + service.inject(child, "key"); + service.inject(root, "key"); + } + + service.dispose(root); + }); + + bench("interleaved append/remove (1000 ops)", () => { + const root = service.createOwner(null); + const list: OwnershipNode[] = []; + + for (let i = 0; i < 1000; i++) { + const child = service.createOwner(root); + list.push(child); + + if (i % 5 === 0 && list.length > 1) { + const toRemove = list.shift()!; + service.removeChild(root, toRemove); + } + } + + service.dispose(root); + }); + + bench("simulate UI component tree", () => { + const root = service.createOwner(null); + + // Header + const header = service.createOwner(root); + for (let i = 0; i < 50; i++) service.createOwner(header); + + // Main + const main = service.createOwner(root); + for (let s = 0; s < 10; s++) { + const section = service.createOwner(main); + for (let i = 0; i < 20; i++) { + service.createOwner(section); + } + } + + // Footer + const footer = service.createOwner(root); + for (let i = 0; i < 30; i++) service.createOwner(footer); + + service.dispose(root); + }); + + bench("subscription cleanup pattern (100 cleanups)", () => { + const owner = service.createOwner(null); + + for (let i = 0; i < 100; i++) { + service.onScopeCleanup(owner, () => {}); + } + + service.dispose(owner); + }); +}); diff --git a/packages/@reflex/core/tests/ownership/ownership.run.ts b/packages/@reflex/core/tests/ownership/ownership.run.ts new file mode 100644 index 0000000..2efa204 --- /dev/null +++ b/packages/@reflex/core/tests/ownership/ownership.run.ts @@ -0,0 +1,30 @@ +// ownership.run.ts +// Чистый нагрузочный прогон без Vitest. +// Запускается через: +// pnpm exec 0x -- node --require ts-node/register/transpile-only tests/ownership.run.ts + +import { createOwner } from "../../src/ownership/ownership.core" + +function build1m() { + const root = createOwner(); + let layer = [root]; + + // 1 + 10 + 100 + 1000 + 10000 + 100000 + 1000000 = 1 111 111 узлов + for (let d = 0; d < 6; d++) { + const next = []; + for (const p of layer) { + for (let i = 0; i < 10; i++) { + next.push(createOwner(p)); + } + } + layer = next; + } + + root.dispose(); +} + +for (let i = 0; i < 10; i++) { + build1m(); +} + +console.log("bench_1m finished"); \ No newline at end of file diff --git a/packages/@reflex/core/tests/storage/uint64array.bench.ts b/packages/@reflex/core/tests/storage/uint64array.bench.ts new file mode 100644 index 0000000..15fd23f --- /dev/null +++ b/packages/@reflex/core/tests/storage/uint64array.bench.ts @@ -0,0 +1,132 @@ +import { bench, describe } from "vitest"; +import { Uint64Array as ReflexU64 } from "../../src/storage/storage.structure"; + +const N = 1_000_000; + +// helper for ns/op +function measure(fn: () => void): number { + const start = performance.now(); + fn(); + const end = performance.now(); + return ((end - start) * 1e6) / N; // ns/op +} + +describe("Uint64Array — precise per-operation benchmarks", () => { + + bench("write() — ns/op", () => { + const S = new ReflexU64(1); + const id = S.create(); + + // warmup + for (let i = 0; i < 1000; i++) S.write(id, i, i); + + const ns = measure(() => { + for (let i = 0; i < N; i++) { + S.write(id, i, i * 7); + } + }); + + console.log(`write(): ${ns.toFixed(2)} ns/op`); + }); + + bench("rawHi/rawLo read — ns/op", () => { + const S = new ReflexU64(1); + const id = S.create(); + S.write(id, 123, 456); + + let sink = 0; + + // warmup + for (let i = 0; i < 1000; i++) { + sink ^= S.rawLo(id); + } + + const ns = measure(() => { + for (let i = 0; i < N; i++) { + sink ^= S.rawHi(id); + sink ^= S.rawLo(id); + } + }); + + if (sink === -1) throw new Error(); + console.log(`rawHi/rawLo: ${ns.toFixed(2)} ns/op`); + }); + + bench("readBigInt — ns/op", () => { + const S = new ReflexU64(1); + const id = S.create(); + S.write(id, 0x11223344, 0xaabbccd0); + + let sink = 0n; + + // warmup + for (let i = 0; i < 1000; i++) sink ^= S.readBigInt(id); + + const ns = measure(() => { + for (let i = 0; i < N; i++) { + sink ^= S.readBigInt(id); + } + }); + + if (sink === -1n) throw new Error(); + console.log(`readBigInt(): ${ns.toFixed(2)} ns/op`); + }); + + bench("writeBigInt — ns/op", () => { + const S = new ReflexU64(1); + const id = S.create(); + + let v = 0n; + + // warmup + for (let i = 0; i < 1000; i++) S.writeBigInt(id, 123n); + + const ns = measure(() => { + for (let i = 0; i < N; i++) { + S.writeBigInt(id, v); + v = (v + 1n) & ((1n << 64n) - 1n); + } + }); + + console.log(`writeBigInt(): ${ns.toFixed(2)} ns/op`); + }); + + bench("readNumber — ns/op", () => { + const S = new ReflexU64(1); + const id = S.create(); + S.write(id, 10, 20); + + let sink = 0; + + // warmup + for (let i = 0; i < 1000; i++) sink ^= S.readNumber(id); + + const ns = measure(() => { + for (let i = 0; i < N; i++) { + sink ^= S.readNumber(id); + } + }); + + if (sink === -1) throw new Error(); + console.log(`readNumber(): ${ns.toFixed(2)} ns/op`); + }); + + bench("writeNumber — ns/op", () => { + const S = new ReflexU64(1); + const id = S.create(); + + let x = 0; + + // warmup + for (let i = 0; i < 1000; i++) S.writeNumber(id, 123); + + const ns = measure(() => { + for (let i = 0; i < N; i++) { + S.writeNumber(id, x++); + } + }); + + console.log(`writeNumber(): ${ns.toFixed(2)} ns/op`); + }); + +}); diff --git a/packages/@reflex/core/tests/storage/uint64array.test.ts b/packages/@reflex/core/tests/storage/uint64array.test.ts new file mode 100644 index 0000000..3756eaf --- /dev/null +++ b/packages/@reflex/core/tests/storage/uint64array.test.ts @@ -0,0 +1,124 @@ +import { describe, it, expect } from "vitest"; +import { Uint64Array } from "../../src/storage/storage.structure"; + +describe("Uint64Array — core semantics", () => { + it("creates empty storage with correct capacity", () => { + const S = new Uint64Array(4); + expect(S.size).toBe(0); + expect(S.capacity).toBe(4); + expect(S.memoryUsage).toBe(4 * 2 * 4); + }); + + it("allocates IDs sequentially", () => { + const S = new Uint64Array(2); + const id0 = S.create(); + const id1 = S.create(); + expect(id0).toBe(0); + expect(id1).toBe(1); + expect(S.size).toBe(2); + }); + + it("grows capacity automatically", () => { + const S = new Uint64Array(1); + S.create(); // ok + expect(S.capacity).toBe(1); + + S.create(); // triggers grow: 1 -> 2 + expect(S.capacity).toBe(2); + }); + + it("write()/readBigInt() works correctly", () => { + const S = new Uint64Array(8); + const id = S.create(); + + const value = 1234567890123456789n & ((1n << 64n) - 1n); + S.writeBigInt(id, value); + + const out = S.readBigInt(id); + expect(out).toBe(value); + }); + + it("writeNumber()/readNumber() matches for safe integers", () => { + const S = new Uint64Array(8); + const id = S.create(); + const value = Number.MAX_SAFE_INTEGER; // 2^53 - 1 + + S.writeNumber(id, value); + expect(S.readNumber(id)).toBe(value); + }); + + it("rawHi/rawLo/setHi/setLo are correct", () => { + const S = new Uint64Array(8); + const id = S.create(); + + S.setHi(id, 0xdeadbeef); + S.setLo(id, 0xcafebabe); + + expect(S.rawHi(id)).toBe(0xdeadbeef >>> 0); + expect(S.rawLo(id)).toBe(0xcafebabe >>> 0); + }); + + it("write() stores correct hi/lo", () => { + const S = new Uint64Array(4); + const id = S.create(); + S.write(id, 0x11223344, 0xaabbccdd); + + expect(S.rawHi(id)).toBe(0x11223344); + expect(S.rawLo(id)).toBe(0xaabbccdd); + }); + + it("copyFrom() copies ranges", () => { + const A = new Uint64Array(8); + const B = new Uint64Array(8); + + const a0 = A.create(); + const a1 = A.create(); + A.write(a0, 1, 2); + A.write(a1, 3, 4); + + B.copyFrom(A, 0, 0, 2); + + expect(B.readBigInt(0)).toBe(A.readBigInt(0)); + expect(B.readBigInt(1)).toBe(A.readBigInt(1)); + expect(B.size).toBe(2); + }); + + it("fill() works", () => { + const S = new Uint64Array(8); + S.create(); + S.create(); + S.create(); + + S.fill(0xaaaa, 0xbbbb); + + expect(S.rawHi(0)).toBe(0xaaaa); + expect(S.rawHi(1)).toBe(0xaaaa); + expect(S.rawHi(2)).toBe(0xaaaa); + expect(S.rawLo(0)).toBe(0xbbbb); + }); + + it("subarray() returns correct view", () => { + const S = new Uint64Array(8); + S.create(); + S.create(); + S.write(0, 1, 2); + S.write(1, 3, 4); + + const view = S.subarray(0, 2); + expect(view.length).toBe(4); // hi0, lo0, hi1, lo1 + expect(view[0]).toBe(1); + expect(view[1]).toBe(2); + }); + + it("clear() resets size but preserves memory", () => { + const S = new Uint64Array(4); + S.create(); + S.create(); + expect(S.size).toBe(2); + + const oldMem = S.memoryUsage; + S.clear(); + expect(S.size).toBe(0); + expect(S.memoryUsage).toBe(oldMem); + }); +}); diff --git a/packages/@reflex/core/tsconfig.json b/packages/@reflex/core/tsconfig.json new file mode 100644 index 0000000..c7a3b7f --- /dev/null +++ b/packages/@reflex/core/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "useUnknownInCatchVariables": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "allowImportingTsExtensions": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "composite": true + }, + "include": ["src", "tests", "test"], + "exclude": ["dist", "**/*.test.ts"] +} diff --git a/packages/@reflex/core/vite.config.ts b/packages/@reflex/core/vite.config.ts new file mode 100644 index 0000000..6150f9a --- /dev/null +++ b/packages/@reflex/core/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from "vite"; + +export default defineConfig({}); diff --git a/packages/@reflex/runtime/package.json b/packages/@reflex/runtime/package.json new file mode 100644 index 0000000..d941efb --- /dev/null +++ b/packages/@reflex/runtime/package.json @@ -0,0 +1,23 @@ +{ + "name": "@reflex/runtime", + "version": "0.1.0", + "type": "module", + "description": "Runtime / Universe / Laws for Reflex", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": ["dist"], + "scripts": { + "build": "tsc -p tsconfig.build.json" + }, + "dependencies": { + "@reflex/core": "workspace:*", + "@reflex/contract": "workspace:*" + } +} diff --git a/packages/@reflex/runtime/src/README.md b/packages/@reflex/runtime/src/README.md new file mode 100644 index 0000000..6b6121c --- /dev/null +++ b/packages/@reflex/runtime/src/README.md @@ -0,0 +1,3 @@ +Runtime — the execution layer of Reflex. +Handles reactivity, scheduling, transactions, and event orchestration. +Connects the logical core with real-world adapters. \ No newline at end of file diff --git a/third-party/rigidify/src/layers/api.ts b/packages/@reflex/runtime/src/execution/context.scope.ts similarity index 100% rename from third-party/rigidify/src/layers/api.ts rename to packages/@reflex/runtime/src/execution/context.scope.ts diff --git a/third-party/rigidify/src/layers/utils/type_code.ts b/packages/@reflex/runtime/src/execution/context.stack.ts similarity index 100% rename from third-party/rigidify/src/layers/utils/type_code.ts rename to packages/@reflex/runtime/src/execution/context.stack.ts diff --git a/packages/@reflex/runtime/src/index.runtime.ts b/packages/@reflex/runtime/src/index.runtime.ts new file mode 100644 index 0000000..29f1136 --- /dev/null +++ b/packages/@reflex/runtime/src/index.runtime.ts @@ -0,0 +1,27 @@ +class Runtime { + readonly layout: ICausalLayout; + readonly graph: IGraph; + readonly scheduler: IScheduler; + + constructor(layoutCapacity: number, graph: IGraph, scheduler: IScheduler) { + this.layout.alloc(layoutCapacity); + this.graph = graph; + this.scheduler = scheduler; + } + + createGraphNode() {} +} + +export default Runtime; + +// const AppRuntime = createReactiveRuntime(); +// const WorkerRuntime = createReactiveRuntime(); + +// AppRuntime.beginComputation(myReaction); +// AppRuntime.track(signalA); +// AppRuntime.endComputation(); + +// // worker работает независимо +// WorkerRuntime.beginComputation(otherReaction); +// WorkerRuntime.track(signalB); +// // WorkerRuntime.endComputation(); diff --git a/packages/@reflex/runtime/src/primitive/computed.ts b/packages/@reflex/runtime/src/primitive/computed.ts new file mode 100644 index 0000000..b39efde --- /dev/null +++ b/packages/@reflex/runtime/src/primitive/computed.ts @@ -0,0 +1,43 @@ +import { GraphNode, IReactiveNode } from "../../core/graph/graph.types"; +import { IOwnership } from "../../core/ownership/ownership.type"; + +class Computed { + private readonly owner: IOwnership | null; + private readonly _node: IReactiveNode; + private readonly computeFn: () => T; + private cachedValue: T | null; + + constructor( + owner: IOwnership | null, + computeFn: () => T, + node: IReactiveNode, + ) { + this.owner = owner; + this._node = node; + this.computeFn = computeFn; + this.cachedValue = null; + } + + get(): T { + if (this.cachedValue === null) { + return this.compute(); + } + + return this.cachedValue; + } + + compute(): T { + const newValue = this.computeFn(); + this.cachedValue = newValue; + return newValue; + } +} + +export function createComputed( + owner: IOwnership | null, + computeFn: () => T, +): () => T { + const graphNode = new GraphNode(); + const computed = new Computed(owner, computeFn, graphNode); + return () => computed.get(); +} \ No newline at end of file diff --git a/packages/@reflex/runtime/src/primitive/effect.ts b/packages/@reflex/runtime/src/primitive/effect.ts new file mode 100644 index 0000000..e480c27 --- /dev/null +++ b/packages/@reflex/runtime/src/primitive/effect.ts @@ -0,0 +1,20 @@ +class Effect { + private effectFn: () => void; + private cleanupFn: (() => void) | null; + + constructor(effectFn: () => void) { + this.effectFn = effectFn; + this.cleanupFn = null; + } + + run(): void { + if (this.cleanupFn) { + this.cleanupFn(); + } + + const cleanup = this.effectFn(); + if (typeof cleanup === "function") { + this.cleanupFn = cleanup; + } + } +} diff --git a/packages/@reflex/runtime/src/primitive/signal.ts b/packages/@reflex/runtime/src/primitive/signal.ts new file mode 100644 index 0000000..e56c5a4 --- /dev/null +++ b/packages/@reflex/runtime/src/primitive/signal.ts @@ -0,0 +1,54 @@ +class Signal { + private value: T; + private readonly owner: IOwnership | null; + private readonly _node: GraphNode; + + constructor(value: T, owner: IOwnership | null, node: GraphNode) { + this.value = value; + this.owner = owner; + this._node = node; + } + + dispose(): void { + // cleanup logic here + } + + get(): T { + return this.value; + } + + set(value: T): void { + // will started a loooong work here... + } +} + +class ReactiveValue { + constructor(private signal: Signal) {} + + get() { + return this.signal.get(); + } + + set(v: T) { + return this.signal.set(v); + } +} + +export function createSignal(initial: T): IReactiveValue { + const { layout, graph, scheduler } = RUNTIME; + + const index = layout.alloc(); + const node = graph.createNode(index); + + const signal = new Signal(initial, node, layout, scheduler); + + function read(): T { + return signal.get(); + } + const reactive = read as IReactiveValue; + reactive.set = (v: T) => signal.set(v); + reactive.node = node; + + owner?.onScopeCleanup(() => signal.cleanup()); + return reactive; +} diff --git a/packages/@reflex/runtime/src/primitive/types.ts b/packages/@reflex/runtime/src/primitive/types.ts new file mode 100644 index 0000000..3ad3483 --- /dev/null +++ b/packages/@reflex/runtime/src/primitive/types.ts @@ -0,0 +1,15 @@ +/** + * @file graph.types.ts + * + * Runtime definitions for the Reflex reactive graph. + */ +type IObserverFn = () => void; + +interface IReactiveValue { + (): T; + (next: T | ((prev: T) => T)): void; + get(): T; + set(next: T | ((prev: T) => T)): void; +} + +export type { IObserverFn, IReactiveValue }; diff --git a/packages/@reflex/runtime/src/runtime.ts b/packages/@reflex/runtime/src/runtime.ts new file mode 100644 index 0000000..eb6d4fc --- /dev/null +++ b/packages/@reflex/runtime/src/runtime.ts @@ -0,0 +1,17 @@ +import { + IRuntime, + IScheduler, + IAllocator, + IGraph, + INode, +} from "@reflex/contract"; + +// це трошки якась зараз хуйня + +export class Runtime implements IRuntime { + constructor( + public readonly scheduler: IScheduler, + public readonly allocator: IAllocator, + public readonly topology: IGraph, + ) {} +} diff --git a/packages/Readme.md b/packages/Readme.md index e69de29..f629617 100644 --- a/packages/Readme.md +++ b/packages/Readme.md @@ -0,0 +1,65 @@ +## Philosophy + +Reflex is not just a UI library. +It is a **reactive runtime** with a clear separation between: + +- **Contracts** – what must hold (laws, invariants, interfaces) +- **Core** – how reactivity is described (signals, ownership, graph) +- **Runtime** – how the system _lives in time_ (scheduler, epochs, execution modes) +- **Adapters** – how this world is projected onto concrete platforms (DOM, etc.) + +> **Important:** `@reflex/core` is _declarative and time-agnostic_ +> It does **not** know about time, schedulers, threads, async APIs, or side effects. +> It only expresses structure, dependencies, and ownership. + +This separation lets Reflex operate in three distinct modes: + +- **Library mode** — ergonomic reactive primitives (`signals`, `effects`, `batch`). +- **Framework mode** — customizable runtime policies (scheduling strategies, execution models). +- **Research mode** — swap/extend contracts, causality models, and memory/scheduling semantics. + +The public API remains simple: + +```ts +import { createSignal, createEffect } from "reflex"; +``` + +while the internal layers remain explicit, swappable, and formally defined. + +--- + +## Architecture Diagram (ASCII / dependency chain) + +```text + @reflex/contract + ▲ + │ + @reflex/runtime + ▲ + │ +@reflex/core ◄──────── reflex (public API) + ▲ + │ + reflex-dom (adapter) +``` + +**How to read this:** + +- `@reflex/contract` — formal laws and interfaces (no behavior) +- `@reflex/runtime` — binds time, scheduling, policies to the system +- `@reflex/core` — implements reactive dataflow & ownership (no time, no effects) +- `reflex` — curated public API / facade +- `reflex-dom` — platform adapter (DOM projection) + +In practice: + +- `reflex` re-exports stable, safe parts of `@reflex/core` and `@reflex/runtime` +- `reflex-dom` depends on `reflex`, not on internals +- Power users and contributors may use `@reflex/*` directly + +--- + +## Architecture Diagram (SVG) + +_(Same semantics, visualized)_ +![alt text](/assets/reflex-arch.svg) diff --git a/packages/common/core/errors/Result.ts b/packages/common/core/errors/Result.ts deleted file mode 100644 index 3fa71b7..0000000 --- a/packages/common/core/errors/Result.ts +++ /dev/null @@ -1,49 +0,0 @@ -type Result = - | { success: true; value: T } - | { success: false; error: E }; - -const ok = (value: T): Result => ({ - success: true, - value, -}); - -const err = (error: E): Result => ({ - success: false, - error, -}); - -const map = ( - result: Result, - fn: (value: T) => U -): Result => { - if (result.success) { - return ok(fn(result.value)); - } - - return { success: false, error: result.error }; -}; - -const flatMap = ( - result: Result, - fn: (value: T) => Result -): Result => { - if (result.success) { - return fn(result.value); - } - - return { success: false, error: result.error }; -}; - -const getOrElse = ( - result: Result, - defaultValue: T -): T => (result.success ? result.value : defaultValue); - -export default { - ok, - err, - map, - flatMap, - getOrElse, -} as const; -export type { Result }; \ No newline at end of file diff --git a/packages/common/core/errors/index.ts b/packages/common/core/errors/index.ts deleted file mode 100644 index b15fa58..0000000 --- a/packages/common/core/errors/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -export type Result = - | { success: true; value: T } - | { success: false; error: E }; - -export type ResultWithMeta = Result & { - meta?: M; -}; - -export function hasError( - result: Result -): result is { success: false; error: E } { - return result.success === false; -} - -const Result = { - ok: (value: T): Result => ({ success: true, value }), - - err: (error: E): Result => ({ - success: false, - error, - }), - - map: (result: Result, fn: (value: T) => U): Result => - result.success ? Result.ok(fn(result.value)) : result, - - flatMap: ( - result: Result, - fn: (value: T) => Result - ): Result => (result.success ? fn(result.value) : result), - - getOrElse: (result: Result, defaultValue: T): T => - result.success ? result.value : defaultValue, -}; diff --git a/packages/common/core/utils/hash/fnv1aHashBytes.ts b/packages/common/core/utils/hash/fnv1aHashBytes.ts deleted file mode 100644 index 7f1c8c4..0000000 --- a/packages/common/core/utils/hash/fnv1aHashBytes.ts +++ /dev/null @@ -1,27 +0,0 @@ -const VAL_32_CONST = 0x811c9dc5 >>> 0; -const PRIME_32_CONST = 0x1000193 >>> 0; - -export function hash_32_fnv1a_const(str: string): number { - let value = VAL_32_CONST; - - for (let i = 0; i < str.length; i++) { - value ^= (str.charCodeAt(i) & 0xff); - value = Math.imul(value, PRIME_32_CONST) >>> 0; - } - - return value; -} - -const VAL_64_CONST = 0xcbf29ce484222325n; -const PRIME_64_CONST = 0x100000001b3n; - -export function hash_64_fnv1a_const(str: string): bigint { - let value = VAL_64_CONST; - - for (let i = 0; i < str.length; i++) { - value ^= BigInt(str.charCodeAt(i) & 0xff); - value *= PRIME_64_CONST; - } - - return value; -} \ No newline at end of file diff --git a/packages/common/makeStringSet.test.ts b/packages/common/makeStringSet.test.ts deleted file mode 100644 index c712489..0000000 --- a/packages/common/makeStringSet.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import makeStringSet from "./makeStringSet"; - -describe("makeStringSet", () => { - test("корректно ищет существующие строки", () => { - const set = makeStringSet(["apple", "banana", "cherry"]); - expect(set.has("apple")).toBe(true); - expect(set.has("banana")).toBe(true); - expect(set.has("cherry")).toBe(true); - }); - - test("возвращает false для отсутствующих строк", () => { - const set = makeStringSet(["apple", "banana"]); - expect(set.has("orange")).toBe(false); - expect(set.has("grape")).toBe(false); - }); - - test("обрабатывает дубликаты без ошибок", () => { - const set = makeStringSet(["apple", "apple", "banana"]); - expect(set.has("apple")).toBe(true); - expect(set.has("banana")).toBe(true); - expect(set.has("orange")).toBe(false); - }); - - test("корректно работает на пустом множестве", () => { - const set = makeStringSet([]); - expect(set.has("anything")).toBe(false); - }); - - test("корректно работает при большом количестве элементов", () => { - const words = Array.from({ length: 10000 }, (_, i) => `word${i}`); - const set = makeStringSet(words); - - // проверим несколько случайных элементов - expect(set.has("word0")).toBe(true); - expect(set.has("word5000")).toBe(true); - expect(set.has("word9999")).toBe(true); - expect(set.has("not_in_set")).toBe(false); - }); - - test("устойчив к коллизиям хэшей (принудительно)", () => { - // подменим хэш-функцию, чтобы вызывать коллизии - jest.mock("./core/utils/hash/fnv1aHashBytes", () => ({ - hash_32_fnv1a_const: (str: string) => str.length - })); - const { default: makeStringSetColl } = require("./makeStringSet"); - - const set = makeStringSetColl(["a", "bb", "ccc"]); - expect(set.has("a")).toBe(true); - expect(set.has("bb")).toBe(true); - expect(set.has("ccc")).toBe(true); - expect(set.has("dddd")).toBe(false); - }); -}); diff --git a/packages/common/makeStringSet.ts b/packages/common/makeStringSet.ts deleted file mode 100644 index 2c24d90..0000000 --- a/packages/common/makeStringSet.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { hash_32_fnv1a_const } from "./core/utils/hash/fnv1aHashBytes"; - -type HasSet = { - has: (str: string) => boolean; -}; - -function nextClosestPow2(n: number): number { - if (n <= 1) { - return 1; - } - - let v = --n; - - for (let i = 1; i < 32; i <<= 1) { - v |= v >> i; - } - - return v + 1; -} - -export default function makeStringSet(strings: string[]): HasSet { - const size = strings.length; - const cap = nextClosestPow2(size * 2); - const mask = cap - 1; - - const table: (string | null)[] = new Array(cap).fill(null); - - for (const s of strings) { - const hash = hash_32_fnv1a_const(s); - let idx = hash & mask; - - while (true) { - const cur = table[idx]; - - if (cur === null) { - table[idx] = s; - break; - } - - if (cur === s) { - break; - } - - idx = (idx + 1) & mask; - } - } - - const has = (str: string): boolean => { - const hash = hash_32_fnv1a_const(str); - let idx = hash & mask; - - while (true) { - const cur = table[idx]; - - if (cur === null) { - return false; - } - - if (cur === str) { - return true; - } - - idx = (idx + 1) & mask; - } - } - - return { has }; -} \ No newline at end of file diff --git a/packages/common/package.json b/packages/common/package.json deleted file mode 100644 index d4e0ddb..0000000 --- a/packages/common/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "private": true, - "name": "common", - "version": "0.0.0" -} \ No newline at end of file diff --git a/packages/reflex-dom/.editorconfig b/packages/reflex-dom/.editorconfig deleted file mode 100644 index 003897b..0000000 --- a/packages/reflex-dom/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -end_of_line = lf -charset = utf-8 -insert_final_newline = true -trim_trailing_whitespace = true - -[{*.js,*.mjs,*.ts,*.json,*.yml}] -indent_size = 2 -indent_style = space \ No newline at end of file diff --git a/packages/reflex-dom/.gitignore copy b/packages/reflex-dom/.gitignore copy deleted file mode 100644 index 524f91a..0000000 --- a/packages/reflex-dom/.gitignore copy +++ /dev/null @@ -1,22 +0,0 @@ -.DS_STORE -node_modules -.flowconfig -*~ -*.pyc -.grunt -_SpecRunner.html -__benchmarks__ -build/ -remote-repo/ -coverage/ -*.log* -*.sublime-project -*.sublime-workspace -.idea -*.iml -.vscode -*.swp -*.swo -drafts/ -package-lock.json -mails/ \ No newline at end of file diff --git a/packages/reflex-dom/.npmignore b/packages/reflex-dom/.npmignore deleted file mode 100644 index 84784ea..0000000 --- a/packages/reflex-dom/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -*.log -.DS_Store -CHANGELOG.md \ No newline at end of file diff --git a/packages/reflex-dom/LICENSE b/packages/reflex-dom/LICENSE deleted file mode 100644 index 8a83559..0000000 --- a/packages/reflex-dom/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Andrii Volynets - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/packages/reflex-dom/examples/Components.tsx b/packages/reflex-dom/examples/Components.tsx new file mode 100644 index 0000000..0b94732 --- /dev/null +++ b/packages/reflex-dom/examples/Components.tsx @@ -0,0 +1,34 @@ +// @ts-ignore + +type FC

= (p: P) => unknown; + +const ArrowComponent: FC<{}> = ({ }) => { + const [value, setValue] = createSignal(0); + + createEffect(() => { + console.log(value()) + }) + + return ( +

prev + 1)}> + {value} +
+ ); +} + +function FucntionComponent({ id, someValue }: { types }) { + const [value, setValue] = createSignal(0); + + createEffect(() => { + console.log(value()) + }) + + return ( +
+
prev + 1)}> + {value} +
+ +
+ ); +} \ No newline at end of file diff --git a/packages/reflex-dom/package.json b/packages/reflex-dom/package.json index 264ff88..4834092 100644 --- a/packages/reflex-dom/package.json +++ b/packages/reflex-dom/package.json @@ -1,9 +1,22 @@ { - "name": "@scope/reflex-dom", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "name": "reflex-dom", + "version": "0.1.0", + "type": "module", + "description": "DOM renderer for Reflex", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, "files": ["dist"], "scripts": { - "build": "tsc --build" + "build": "tsc -p tsconfig.build.json" + }, + "dependencies": { + "reflex": "workspace:*" } } diff --git a/packages/reflex-dom/src/client/layoutTrashing.ts b/packages/reflex-dom/src/client/layoutTrashing.ts index 6774d68..29cef9c 100644 --- a/packages/reflex-dom/src/client/layoutTrashing.ts +++ b/packages/reflex-dom/src/client/layoutTrashing.ts @@ -1,105 +1,215 @@ -const sharedLayoutMethods = [ - "getClientRects", - "getBoundingClientRect", -] as const; - -const overflowScrollMethods = [ - "scrollBy", - "scrollTo", - "scrollIntoView", - "scrollIntoViewIfNeeded", -] as const; - -const layoutTrashing = { - Element: { - props: [ - "clientLeft", - "clientTop", - "clientWidth", - "clientHeight", - "scrollWidth", - "scrollHeight", - "scrollLeft", - "scrollTop", - "textContent", - ] as const, - methods: [...sharedLayoutMethods, ...overflowScrollMethods], - }, +type LayoutCategory = "read" | "write" | "mixed"; - HTMLElement: { - props: [ - "offsetLeft", - "offsetTop", - "offsetWidth", - "offsetHeight", - "offsetParent", - "compiledRole", // исправлено: computedRole? - "compiledName", // исправлено: computedName? - "innerText", - "textContent", - // DOM layout-influencing properties - "style", - "classList", - "dataset", - ] as const, - methods: ["focus"] as const, - }, +interface LayoutProperty { + category: LayoutCategory; + description?: string; + riskLevel: "high" | "medium" | "low"; + alternative?: string; +} - window: { - props: [ - "scrollX", - "scrollY", - "pageXOffset", - "pageYOffset", - "innerWidth", - "innerHeight", - "visualViewport", - ] as const, - methods: ["getComputedStyle"] as const, +const layoutThrashingDatabase = { + reads: { + Element: { + clientLeft: { + category: "read" as const, + riskLevel: "high", + description: + "The width of the left border of the element (including scrollbar)", + }, + clientTop: { + category: "read" as const, + riskLevel: "high", + description: "The height of the top border of the element", + }, + clientWidth: { + category: "read" as const, + riskLevel: "high", + description: "The width of the element's content area", + }, + clientHeight: { + category: "read" as const, + riskLevel: "high", + description: "The height of the element's content area", + }, + scrollWidth: { + category: "read" as const, + riskLevel: "high", + description: "The total width of the scrollable content", + }, + scrollHeight: { + category: "read" as const, + riskLevel: "high", + description: "The total height of the scrollable content", + }, + scrollLeft: { + category: "read" as const, + riskLevel: "high", + description: "The horizontal scroll position", + }, + scrollTop: { + category: "read" as const, + riskLevel: "high", + description: "The vertical scroll position", + }, + }, }, - VisualViewport: { - props: [ - "width", - "height", - "offsetLeft", - "offsetTop", - "pageLeft", - "pageTop", - ] as const, + writes: { + Element: { + scrollLeft: { + category: "write" as const, + riskLevel: "high", + description: "Sets the horizontal scroll position", + alternative: "scrollTo(), scrollBy()", + }, + scrollTop: { + category: "write" as const, + riskLevel: "high", + description: "Sets the vertical scroll position", + alternative: "scrollTo(), scrollBy()", + }, + textContent: { + category: "write" as const, + riskLevel: "high", + description: "Changes the text content of the element", + alternative: "innerText, createTextNode()", + }, + }, }, - Document: { - props: ["scrollingElement"] as const, - methods: ["elementFromPoint"] as const, + methods: { + Element: { + getClientRects: { + category: "read" as const, + riskLevel: "high", + description: "Gets the coordinates of all boxes of the element", + }, + getBoundingClientRect: { + category: "read" as const, + riskLevel: "high", + description: "Gets the coordinates of the element", + }, + scrollBy: { + category: "write" as const, + riskLevel: "high", + description: "Scrolls by a specified amount", + }, + scrollTo: { + category: "write" as const, + riskLevel: "high", + description: "Scrolls to a specified position", + }, + scrollIntoView: { + category: "write" as const, + riskLevel: "high", + description: "Scrolls the element into view", + alternative: "requestAnimationFrame", + }, + scrollIntoViewIfNeeded: { + category: "write" as const, + riskLevel: "high", + description: "Conditionally scrolls the element into view", + alternative: "requestAnimationFrame", + }, + }, }, +} as const; - HTMLInputElement: { - methods: ["select", "focus"] as const, - }, +type Category = "reads" | "writes" | "methods" - MouseEvent: { - props: [ - "offsetX", - "offsetY", - "layerX", - "layerY", - "clientX", - "clientY", - "pageX", - "pageY", - ] as const, - }, +/** + * Get all properties/methods of a specific category + */ +function getAllLayoutOperations(category: Category) { + return layoutThrashingDatabase[category]; +} - Range: { - methods: [...sharedLayoutMethods] as const, - }, -} as const; +/** + * Check if an operation is potentially problematic + */ +function isLayoutThrashing( + target: string, + property: string, + type: "read" | "write" | "method" = "read" +): boolean { + const db = layoutThrashingDatabase; -function getLayoutProps( - target: T -): readonly string[] { - const obj = layoutTrashing[target]; + if (type === "read" && "reads" in db) { + return property in (db.reads[target as keyof typeof db.reads] || {}); + } - return "props" in obj ? (obj.props as readonly string[]) : []; + if (type === "write" && "writes" in db) { + return property in (db.writes[target as keyof typeof db.writes] || {}); + } + + if (type === "method" && "methods" in db) { + return property in (db.methods[target as keyof typeof db.methods] || {}); + } + + return false; } + +/** + * Get the risk level of an operation + */ +function getRiskLevel( + target: string, + property: string +): "high" | "medium" | "low" | null { + const reads = layoutThrashingDatabase.reads as any; + const writes = layoutThrashingDatabase.writes as any; + const methods = layoutThrashingDatabase.methods as any; + + if (reads[target]?.[property]) return reads[target][property].riskLevel; + if (writes[target]?.[property]) return writes[target][property].riskLevel; + if (methods[target]?.[property]) return methods[target][property].riskLevel; + + return null; +} + +/** + * Get description and alternative for an operation + */ +function getOperationInfo(target: string, property: string) { + const reads = layoutThrashingDatabase.reads as any; + const writes = layoutThrashingDatabase.writes as any; + const methods = layoutThrashingDatabase.methods as any; + + return ( + reads[target]?.[property] || + writes[target]?.[property] || + methods[target]?.[property] || + null + ); +} + +/** + * Get all high-risk operations + */ +function getHighRiskOperations() { + const result: Record = {}; + const db = layoutThrashingDatabase as any; + + for (const category of Object.keys(db)) { + for (const target of Object.keys(db[category])) { + for (const [prop, info] of Object.entries(db[category][target])) { + if ((info as any).riskLevel === "high") { + if (!result[target]) result[target] = []; + result[target].push(prop); + } + } + } + } + + return result; +} + +export { + layoutThrashingDatabase, + isLayoutThrashing, + getRiskLevel, + getOperationInfo, + getHighRiskOperations, + type LayoutProperty, + type LayoutCategory, +}; diff --git a/packages/reflex-dom/src/client/sanitize.ts b/packages/reflex-dom/src/client/sanitize.ts index d73ddd9..1912471 100644 --- a/packages/reflex-dom/src/client/sanitize.ts +++ b/packages/reflex-dom/src/client/sanitize.ts @@ -3,7 +3,7 @@ * Prevents code execution if a javascript: URL is accidentally visited. */ const BLOCKED_JS_URL = - "javascript:throw new Error('Blocked javascript: URL for security.')"; + "javascript:throw new Error('Blocked javascript: URL for security.')" /** * Checks if a character is a C0 control character or space (U+0000 to U+001F, U+0020). @@ -52,7 +52,7 @@ export function sanitizeURL(url: T): T | string { i++; } - const proto = "javascript:"; + const proto = "javascript:" const protoLength = proto.length; let j = 0; diff --git a/packages/reflex-dom/src/client/tags.ts b/packages/reflex-dom/src/client/tags.ts index ef5b834..cb45423 100644 --- a/packages/reflex-dom/src/client/tags.ts +++ b/packages/reflex-dom/src/client/tags.ts @@ -1,242 +1,245 @@ -const HTML_TAGS = { - a: true, - abbr: true, - address: true, - area: true, - article: true, - aside: true, - audio: true, - b: true, - base: true, - bdi: true, - bdo: true, - blockquote: true, - body: true, - br: true, - button: true, - canvas: true, - caption: true, - cite: true, - code: true, - col: true, - colgroup: true, - data: true, - datalist: true, - dd: true, - del: true, - details: true, - dfn: true, - dialog: true, - div: true, - dl: true, - dt: true, - em: true, - embed: true, - fieldset: true, - figcaption: true, - figure: true, - footer: true, - form: true, - h1: true, - h2: true, - h3: true, - h4: true, - h5: true, - h6: true, - head: true, - header: true, - hgroup: true, - hr: true, - html: true, - i: true, - iframe: true, - img: true, - input: true, - ins: true, - kbd: true, - label: true, - legend: true, - li: true, - link: true, - main: true, - map: true, - mark: true, - menu: true, - meta: true, - meter: true, - nav: true, - noscript: true, - object: true, - ol: true, - optgroup: true, - option: true, - output: true, - p: true, - param: true, - picture: true, - pre: true, - progress: true, - q: true, - rb: true, - rp: true, - rt: true, - rtc: true, - ruby: true, - s: true, - samp: true, - script: true, - search: true, - section: true, - select: true, - slot: true, - small: true, - source: true, - span: true, - strong: true, - style: true, - sub: true, - summary: true, - sup: true, - table: true, - tbody: true, - td: true, - template: true, - textarea: true, - tfoot: true, - th: true, - thead: true, - time: true, - title: true, - tr: true, - track: true, - u: true, - ul: true, - var: true, - video: true, - wbr: true, -} as const; +/** Creates a null-prototype lookup object for maximal speed */ +function makeLookup(items: readonly string[]) { + const table: Record = Object.create(null); -const SVG_TAGS = { - a: true, - animate: true, - animateMotion: true, - animateTransform: true, - circle: true, - clipPath: true, - defs: true, - desc: true, - ellipse: true, - feBlend: true, - feColorMatrix: true, - feComponentTransfer: true, - feComposite: true, - feConvolveMatrix: true, - feDiffuseLighting: true, - feDisplacementMap: true, - feDistantLight: true, - feDropShadow: true, - feFlood: true, - feFuncA: true, - feFuncB: true, - feFuncG: true, - feFuncR: true, - feGaussianBlur: true, - feImage: true, - feMerge: true, - feMergeNode: true, - feMorphology: true, - feOffset: true, - fePointLight: true, - feSpecularLighting: true, - feSpotLight: true, - feTile: true, - feTurbulence: true, - filter: true, - font: true, - "font-face": true, - foreignObject: true, - g: true, - glyph: true, - glyphRef: true, - hkern: true, - image: true, - line: true, - linearGradient: true, - marker: true, - mask: true, - metadata: true, - "missing-glyph": true, - mpath: true, - path: true, - pattern: true, - polygon: true, - polyline: true, - radialGradient: true, - rect: true, - script: true, - set: true, - stop: true, - style: true, - svg: true, - switch: true, - symbol: true, - text: true, - textPath: true, - title: true, - tref: true, - tspan: true, - use: true, - view: true, - vkern: true, -} as const; + for (let i = 0, len = items.length; i < len; i++) { + table[items[i]!] = true; + } -const VOID_TAGS = { - area: true, - base: true, - br: true, - col: true, - embed: true, - hr: true, - img: true, - input: true, - link: true, - meta: true, - param: true, - source: true, - track: true, - wbr: true, -} as const; + return table; +} -type HTMLTag = keyof typeof HTML_TAGS; -type SVGTag = keyof typeof SVG_TAGS; -type VoidTag = keyof typeof VOID_TAGS; +const HTML_TAGS = makeLookup([ + "a", + "abbr", + "address", + "area", + "article", + "aside", + "audio", + "b", + "base", + "bdi", + "bdo", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "label", + "legend", + "li", + "link", + "main", + "map", + "mark", + "menu", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "picture", + "pre", + "progress", + "q", + "rb", + "rp", + "rt", + "rtc", + "ruby", + "s", + "samp", + "script", + "search", + "section", + "select", + "slot", + "small", + "source", + "span", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "video", + "wbr", +] as const); -function hasTag( - tag: T, - type: "html" | "svg" | "void" = "html" -): boolean { - switch (type) { - case "html": - return !!HTML_TAGS[tag as HTMLTag]; - case "svg": - return !!SVG_TAGS[tag as SVGTag]; - case "void": - return !!VOID_TAGS[tag as VoidTag]; - default: - return false; - } -} +const SVG_TAGS = makeLookup([ + "a", + "animate", + "animateMotion", + "animateTransform", + "circle", + "clipPath", + "defs", + "desc", + "ellipse", + "feBlend", + "feColorMatrix", + "feComponentTransfer", + "feComposite", + "feConvolveMatrix", + "feDiffuseLighting", + "feDisplacementMap", + "feDistantLight", + "feDropShadow", + "feFlood", + "feFuncA", + "feFuncB", + "feFuncG", + "feFuncR", + "feGaussianBlur", + "feImage", + "feMerge", + "feMergeNode", + "feMorphology", + "feOffset", + "fePointLight", + "feSpecularLighting", + "feSpotLight", + "feTile", + "feTurbulence", + "filter", + "font", + "font-face", + "foreignObject", + "g", + "glyph", + "glyphRef", + "hkern", + "image", + "line", + "linearGradient", + "marker", + "mask", + "metadata", + "missing-glyph", + "mpath", + "path", + "pattern", + "polygon", + "polyline", + "radialGradient", + "rect", + "script", + "set", + "stop", + "style", + "svg", + "switch", + "symbol", + "text", + "textPath", + "title", + "tref", + "tspan", + "use", + "view", + "vkern", +] as const); + +const VOID_TAGS = makeLookup([ + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "link", + "meta", + "param", + "source", + "track", + "wbr", +] as const); -const isHTMLTag = (tag: HTMLTag): boolean => hasTag(tag, "html"); -const isSVGTag = (tag: SVGTag): boolean => hasTag(tag, "svg"); -const isVoidTag = (tag: VoidTag): boolean => hasTag(tag, "void"); +export type HTMLTag = keyof typeof HTML_TAGS; +export type SVGTag = keyof typeof SVG_TAGS; +export type VoidTag = keyof typeof VOID_TAGS; -export { - isHTMLTag, - isSVGTag, - isVoidTag, - hasTag, - HTMLTag, - SVGTag, - VoidTag, -}; +/** Ultra-fast lookups (branchless except early-exit) */ + +export const isHTMLTag = (tag: string): boolean => + HTML_TAGS[tag as HTMLTag] === true; + +export const isSVGTag = (tag: string): boolean => + SVG_TAGS[tag as SVGTag] === true; + +export const isVoidTag = (tag: string): boolean => + VOID_TAGS[tag as VoidTag] === true; + +/** + * Unified lookup with no switch + */ +export function hasTag(tag: string, type: "html" | "svg" | "void"): boolean { + return type === "html" + ? HTML_TAGS[tag as HTMLTag] === true + : type === "svg" + ? SVG_TAGS[tag as SVGTag] === true + : VOID_TAGS[tag as VoidTag] === true; +} diff --git a/packages/reflex-dom/src/client/validate/DOMNestingClassificator.ts b/packages/reflex-dom/src/client/validate/DOMNestingClassificator.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/reflex-dom/src/common/events/getVendorPrefixedEventName.ts b/packages/reflex-dom/src/common/events/getVendorPrefixedEventName.ts deleted file mode 100644 index 9d8b569..0000000 --- a/packages/reflex-dom/src/common/events/getVendorPrefixedEventName.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { IS_DOM_AVAILABLE } from "../isDomAvailable"; - -/** - * Style object used to detect supported CSS properties. - * If the DOM is unavailable (SSR or test environment), `style` will be empty. - */ -let style: CSSStyleDeclaration | {} = {}; - -if (IS_DOM_AVAILABLE) { - style = document.createElement("div").style; -} - -type VendorPrefixedEvent = - | "animationend" - | "animationiteration" - | "animationstart" - | "transitionend"; - -/** - * A map of modern event names to their possible vendor-prefixed alternatives. - * In 2025, only `Webkit` prefixes may still be relevant for some legacy WebKit-based browsers. - * Other prefixes (e.g., `Moz`) are considered obsolete and are not included. - */ -const vendorMap: Record> = { - animationend: { - animation: "animationend", - WebkitAnimation: "webkitAnimationEnd", - }, - animationiteration: { - animation: "animationiteration", - WebkitAnimation: "webkitAnimationIteration", - }, - animationstart: { - animation: "animationstart", - WebkitAnimation: "webkitAnimationStart", - }, - transitionend: { - transition: "transitionend", - WebkitTransition: "webkitTransitionEnd", - }, -}; - -/** - * Simple cache to avoid repeatedly checking `style` for the same event name. - */ -const cache: Record = {}; - -/** - * Returns the correct event name for the current environment, using vendor prefixes if necessary. - * - * Notes on fallback behavior: - * - In modern browsers (Chrome, Firefox, Edge, Safari), the unprefixed event name is sufficient. - * - `Webkit` prefixes are retained only as a minimal fallback for legacy WebKit-based browsers. - * - If the DOM is unavailable (e.g., server-side rendering), the original event name is returned. - */ -export function getVendorPrefixedEventName(event: VendorPrefixedEvent): string { - if (cache[event]) { - return cache[event]; - } - - const map = vendorMap[event]; - - if (!map) { - return (cache[event] = event); - } - - for (const prop in map) { - if (prop in style) { - return (cache[event] = map[prop]); - } - } - - // Fallback: return the original event name if no supported property is detected - return (cache[event] = event); -} - - - diff --git a/packages/reflex-dom/src/common/isDomAvailable.ts b/packages/reflex-dom/src/common/isDomAvailable.ts deleted file mode 100644 index c149d0f..0000000 --- a/packages/reflex-dom/src/common/isDomAvailable.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Indicates whether the DOM is available. - * This can be useful for determining if certain browser APIs are accessible. - */ -export const IS_DOM_AVAILABLE = typeof globalThis.document !== "undefined"; diff --git a/packages/reflex-dom/src/common/validate/isAttributeNameSafe.ts b/packages/reflex-dom/src/common/validate/isAttributeNameSafe.ts deleted file mode 100644 index b96d14b..0000000 --- a/packages/reflex-dom/src/common/validate/isAttributeNameSafe.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Optional: Precompiled regex for reuse in high-performance scenarios - */ -export const SAFE_ATTRIBUTE_REGEX = /^[A-Za-z_:][A-Za-z0-9_:.-\u00B7]*$/; - -/** - * Validates whether an attribute name is safe according to the specified rules: - * - Start character: A-Z, a-z, _, or : - * - Name characters: Start characters plus 0-9, -, ., or \u00B7 (middle dot) - * - Ensures maximal safety and performance for library usage. - * - * @param attributeName The attribute name to validate - * @returns True if the attribute name is safe, false otherwise - */ -export function isAttributeNameSafe(attributeName: string): boolean { - if (typeof attributeName !== "string" || !attributeName) { - return false; - } - - if (256 < attributeName.length) { - return false; // Prevent excessively long attribute names - } - - return SAFE_ATTRIBUTE_REGEX.test(attributeName); -} - -/** - * Alternative non-regex implementation for specific high-performance use cases - * @param attributeName The attribute name to validate - * @returns True if the attribute name is safe, false otherwise - */ -export function isAttributeNameSafeNonRegex(attributeName: string): boolean { - const len = attributeName.length; - - if (typeof attributeName !== "string" || !attributeName || 256 < len) { - return false; - } - - const firstCharCode = attributeName.charCodeAt(0); - if (!isValidFirstChar(firstCharCode)) { - return false; - } - - for (let i = 1; i < len; i++) { - if (!isValidSubsequentChar(attributeName.charCodeAt(i))) { - return false; - } - } - - return true; -} - -function isValidFirstChar(charCode: number): boolean { - return ( - (charCode >= 65 && charCode <= 90) || // A-Z - (charCode >= 97 && charCode <= 122) || // a-z - charCode === 95 || // _ - charCode === 58 // : - ); -} - -function isValidSubsequentChar(charCode: number): boolean { - return ( - (charCode >= 65 && charCode <= 90) || // A-Z - (charCode >= 97 && charCode <= 122) || // a-z - (charCode >= 48 && charCode <= 57) || // 0-9 - charCode === 95 || // _ - charCode === 58 || // : - charCode === 45 || // - - charCode === 46 || // . - charCode === 183 // · - ); -} diff --git a/packages/reflex-dom/src/dom.d.ts b/packages/reflex-dom/src/dom.d.ts deleted file mode 100644 index 84f3f9f..0000000 --- a/packages/reflex-dom/src/dom.d.ts +++ /dev/null @@ -1,246 +0,0 @@ -// Core DOM Node interface, base for all DOM nodes -export interface DOMNode { - // Node type constants as per https://dom.spec.whatwg.org/#interface-node - readonly nodeType: number; - readonly nodeName: string; - readonly ownerDocument: Document | null; - parentNode: DOMNode | null; - parentElement: Element | null; - readonly childNodes: NodeList; - firstChild: DOMNode | null; - lastChild: DOMNode | null; - previousSibling: DOMNode | null; - nextSibling: DOMNode | null; - nodeValue: string | null; - textContent: string | null; - - // Methods for node manipulation - appendChild(node: T): T; - insertBefore(node: T, child: DOMNode | null): T; - removeChild(child: T): T; - replaceChild(newChild: T, oldChild: DOMNode): T; - cloneNode(deep?: boolean): DOMNode; - contains(other: DOMNode | null): boolean; - isEqualNode(other: DOMNode | null): boolean; - - // Event handling - addEventListener( - type: K, - listener: (this: DOMNode, ev: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions - ): void; - removeEventListener( - type: K, - listener: (this: DOMNode, ev: HTMLElementEventMap[K]) => void, - options?: boolean | EventListenerOptions - ): void; - dispatchEvent(event: Event): boolean; -} - -// Node type constants for clarity and type safety -export enum NodeType { - ELEMENT_NODE = 1, - TEXT_NODE = 3, - COMMENT_NODE = 8, - DOCUMENT_NODE = 9, - DOCUMENT_TYPE_NODE = 10, - DOCUMENT_FRAGMENT_NODE = 11, -} - -// Generic NodeList interface for collections of nodes -export interface NodeList { - readonly length: number; - item(index: number): T | null; - [index: number]: T; - [Symbol.iterator](): Iterator; -} - -// Document interface, representing the root of the DOM tree -export interface Document extends DOMNode { - readonly nodeType: typeof NodeType.DOCUMENT_NODE; - readonly documentElement: Element | null; - readonly body: HTMLElement | null; - readonly head: HTMLHeadElement | null; - defaultView: Window | null; - - createElement(tagName: K): HTMLElementTagNameMap[K]; - createElementNS(namespaceURI: string | null, qualifiedName: string): Element; - createTextNode(data: string): TextNode; - createComment(data: string): CommentNode; - createDocumentFragment(): DocumentFragment; - - getElementById(id: string): Element | null; - querySelector(selectors: K): HTMLElementTagNameMap[K] | null; - querySelector(selectors: string): E | null; - querySelectorAll(selectors: K): NodeList; - querySelectorAll(selectors: string): NodeList; - - adoptNode(node: T): T; - importNode(node: T, deep: boolean): T; -} - -// Element interface, representing HTML or SVG elements -export interface Element extends DOMNode { - readonly nodeType: typeof NodeType.ELEMENT_NODE; - readonly tagName: string; - readonly attributes: NamedNodeMap; - readonly classList: DOMTokenList; - id: string; - className: string; - innerHTML: string; - outerHTML: string; - - getAttribute(name: string): string | null; - setAttribute(name: string, value: string): void; - removeAttribute(name: string): void; - hasAttribute(name: string): boolean; - - querySelector(selectors: K): HTMLElementTagNameMap[K] | null; - querySelector(selectors: string): E | null; - querySelectorAll(selectors: K): NodeList; - querySelectorAll(selectors: string): NodeList; - - getElementsByTagName(tagName: K): NodeList; - getElementsByTagName(tagName: string): NodeList; - getElementsByClassName(className: string): NodeList; - - matches(selectors: string): boolean; - closest(selector: K): HTMLElementTagNameMap[K] | null; - closest(selector: string): E | null; -} - -// HTMLElement interface, extending Element for HTML-specific elements -export interface HTMLElement extends Element { - title: string; - lang: string; - dir: string; - hidden: boolean; - tabIndex: number; - accessKey: string; - - style: CSSStyleDeclaration; - dataset: DOMStringMap; - - click(): void; - focus(options?: FocusOptions): void; - blur(): void; -} - -// Specific HTML element interfaces (subset for brevity) -export interface HTMLHeadElement extends HTMLElement { - readonly tagName: 'HEAD'; -} - -export interface HTMLBodyElement extends HTMLElement { - readonly tagName: 'BODY'; -} - -export interface HTMLAnchorElement extends HTMLElement { - readonly tagName: 'A'; - href: string; - target: string; - rel: string; -} - -export interface HTMLInputElement extends HTMLElement { - readonly tagName: 'INPUT'; - type: string; - value: string; - checked: boolean; - disabled: boolean; -} - -// Text node interface -export interface TextNode extends DOMNode { - readonly nodeType: typeof NodeType.TEXT_NODE; - readonly data: string; - readonly wholeText: string; - splitText(offset: number): TextNode; -} - -// Comment node interface -export interface CommentNode extends DOMNode { - readonly nodeType: typeof NodeType.COMMENT_NODE; - readonly data: string; -} - -// DocumentFragment interface -export interface DocumentFragment extends DOMNode { - readonly nodeType: typeof NodeType.DOCUMENT_FRAGMENT_NODE; -} - -// NamedNodeMap for element attributes -export interface NamedNodeMap { - readonly length: number; - getNamedItem(name: string): Attr | null; - setNamedItem(attr: Attr): void; - removeNamedItem(name: string): Attr; - item(index: number): Attr | null; - [index: number]: Attr; -} - -// Attr interface for element attributes -export interface Attr { - readonly name: string; - value: string; - readonly namespaceURI: string | null; - readonly localName: string; -} - -// DOMTokenList for classList -export interface DOMTokenList { - readonly length: number; - value: string; - add(...tokens: string[]): void; - remove(...tokens: string[]): void; - toggle(token: string, force?: boolean): boolean; - contains(token: string): boolean; - [index: number]: string; - [Symbol.iterator](): Iterator; -} - -// CSSStyleDeclaration for element.style -export interface CSSStyleDeclaration { - cssText: string; - getPropertyValue(property: string): string; - setProperty(property: string, value: string | null, priority?: string): void; - removeProperty(property: string): string; - [index: number]: string; -} - -// DOMStringMap for dataset -export interface DOMStringMap { - [key: string]: string | undefined; -} - -// Event-related interfaces (simplified) -export interface Event { - readonly type: string; - readonly target: DOMNode | null; - readonly currentTarget: DOMNode | null; - preventDefault(): void; - stopPropagation(): void; -} - -export interface AddEventListenerOptions { - capture?: boolean; - once?: boolean; - passive?: boolean; -} - -export interface EventListenerOptions { - capture?: boolean; -} - -// Type map for HTML elements (subset for brevity) -export interface HTMLElementTagNameMap { - 'a': HTMLAnchorElement; - 'body': HTMLBodyElement; - 'head': HTMLHeadElement; - 'input': HTMLInputElement; - 'div': HTMLElement; - 'span': HTMLElement; - 'p': HTMLElement; - // Add more as needed -} - diff --git a/packages/reflex-dom/src/shared/avaiblable.ts b/packages/reflex-dom/src/shared/avaiblable.ts new file mode 100644 index 0000000..d9de9a4 --- /dev/null +++ b/packages/reflex-dom/src/shared/avaiblable.ts @@ -0,0 +1,35 @@ +/** + * Returns true if running in any real browser context. + * (window + document + createElement must exist) + */ +export const IS_BROWSER = + typeof globalThis.window !== "undefined" && + typeof globalThis.document !== "undefined" && + typeof globalThis.document.createElement === "function"; + +/** + * Returns true if DOM-like APIs exist. + * JSDOM → true + * Real browser → true + * Node/Bun/SSR → false + */ +export const IS_DOM_AVAILABLE = IS_BROWSER; + +/** + * Returns true for server-side environments (Node, Bun, Deno). + * Works reliably for SSR setups. + */ +export const IS_SERVER = !IS_BROWSER; + +/** + * Detects JSDOM specifically. + * JSDOM sets navigator.userAgent containing "jsdom". + * Safe: navigator may not exist → optional checks. + */ +export const IS_JSDOM = + IS_DOM_AVAILABLE && + !!( + globalThis.navigator && + typeof globalThis.navigator.userAgent === "string" && + globalThis.navigator.userAgent.includes("jsdom") + ); diff --git a/packages/reflex-dom/src/shared/events/getVendorPrefixedEventName.ts b/packages/reflex-dom/src/shared/events/getVendorPrefixedEventName.ts new file mode 100644 index 0000000..679b71a --- /dev/null +++ b/packages/reflex-dom/src/shared/events/getVendorPrefixedEventName.ts @@ -0,0 +1,88 @@ +import { IS_DOM_AVAILABLE } from "../avaiblable"; + +type VendorPrefixedEvent = + | "animationend" + | "animationiteration" + | "animationstart" + | "transitionend"; + +/** + * Один раз создаём style и вычисляем, какие свойства вообще поддерживаются. + */ +const style: CSSStyleDeclaration | null = IS_DOM_AVAILABLE + ? document.createElement("div").style + : null; + +const supports = + style && IS_DOM_AVAILABLE + ? { + animation: "animation" in style, + WebkitAnimation: "WebkitAnimation" in style, + transition: "transition" in style, + WebkitTransition: "WebkitTransition" in style, + } + : null; + +/** + * Кэш по именам событий, чтобы не делать лишнюю логику после первого вызова. + */ +const cache: Partial> = Object.create(null); + +/** + * Возвращает корректное имя события для текущего окружения. + */ +export function getVendorPrefixedEventName(event: VendorPrefixedEvent): string { + const cached = cache[event]; + if (cached) { + return cached; + } + + // SSR / тесты / нет style — ничего не мудрим + if (!supports) { + cache[event] = event; + return event; + } + + let resolved: string; + + switch (event) { + case "animationend": + resolved = supports.animation + ? "animationend" + : supports.WebkitAnimation + ? "webkitAnimationEnd" + : "animationend"; + break; + + case "animationiteration": + resolved = supports.animation + ? "animationiteration" + : supports.WebkitAnimation + ? "webkitAnimationIteration" + : "animationiteration"; + break; + + case "animationstart": + resolved = supports.animation + ? "animationstart" + : supports.WebkitAnimation + ? "webkitAnimationStart" + : "animationstart"; + break; + + case "transitionend": + resolved = supports.transition + ? "transitionend" + : supports.WebkitTransition + ? "webkitTransitionEnd" + : "transitionend"; + break; + + default: + // На случай расширения типів в будущем + resolved = event; + } + + cache[event] = resolved; + return resolved; +} diff --git a/packages/reflex-dom/src/common/validate/DOMNestingClassificator.ts b/packages/reflex-dom/src/shared/validate/DOMNestingClassificator.ts similarity index 83% rename from packages/reflex-dom/src/common/validate/DOMNestingClassificator.ts rename to packages/reflex-dom/src/shared/validate/DOMNestingClassificator.ts index 897a39d..c4626ad 100644 --- a/packages/reflex-dom/src/common/validate/DOMNestingClassificator.ts +++ b/packages/reflex-dom/src/shared/validate/DOMNestingClassificator.ts @@ -6,8 +6,8 @@ import { } from "../../client/nestingRule"; type LookupExistingFlag = 1 & { __brand: "LOOKUP_EXISTING_FLAG" }; - const LOOKUP_EXISTING_FLAG = 1 as LookupExistingFlag; + const SPECIAL_RULES = { RUBY: "__RUBY__", // Ruby annotations DATALIST: "__DATALIST__", // Data list options @@ -15,7 +15,7 @@ const SPECIAL_RULES = { } as const; function makeLookup( - tokens: Iterable + tokens: Iterable, ): Record { const o = Object.create(null) as Record; for (const t of tokens) { @@ -25,17 +25,18 @@ function makeLookup( } function toLookup( - entries: Iterable + entries: Iterable, ): Record { return makeLookup(entries); } function strToLookup( - str: string | undefined + str: string | undefined, ): Record | undefined { if (str == null) return undefined; - if (str === "") + if (str === "") { return Object.create(null) as Record; + } return makeLookup(str.split(/\s+/)); } @@ -50,7 +51,6 @@ const enum AllowedKind { Set = 2, } - const RULE_DATA: Array<[string, AllowedKind, string?, string?]> = [ ["html", AllowedKind.Set, "head body"], [ @@ -209,11 +209,11 @@ interface NormalizedRule { } function normalizeRules( - data: typeof RULE_DATA + data: typeof RULE_DATA, ): Record { const out = Object.create(null) as Record; - for (const [tag, kindNum, allowedList, forbiddenList] of RULE_DATA) { + for (const [tag, kindNum, allowedList, forbiddenList] of data) { let allowedSet: Record | undefined; if (kindNum === AllowedKind.Set) { @@ -240,7 +240,7 @@ function normalizeRules( allowedSet = strToLookup(allowedList); } } - + out[tag] = { kind: kindNum, allowedSet, @@ -251,8 +251,6 @@ function normalizeRules( return out; } -// if (__DEV__) Object.freeze(NORMALIZED_RULES); - interface AncestorInfo { currentTag: string | null; formTag: string | null; @@ -263,48 +261,15 @@ interface AncestorInfo { dlItemTagAutoclosing: string | null; } +/** Горячие lookup-функции без лишних абстракций. */ const isPhrasing = (tag: string): boolean => PHRASING_LOOKUP[tag] === LOOKUP_EXISTING_FLAG; + const isVoid = (tag: string): boolean => VOID_LOOKUP[tag] === LOOKUP_EXISTING_FLAG; const NORMALIZED_RULES = normalizeRules(RULE_DATA); -function isValidChild( - parentTag: string, - childTag: string, - ancestorInfo: AncestorInfo -): boolean { - if (isVoid(parentTag)) { - return false; - } - - const norm = NORMALIZED_RULES[parentTag]; - - if (!norm) return checkContextRestrictions(childTag, ancestorInfo); - - switch (norm.kind) { - case AllowedKind.Any: - break; - case AllowedKind.Phrasing: - if (!isPhrasing(childTag)) { - return false; - } - break; - case AllowedKind.Set: - if (norm.allowedSet?.[childTag] !== LOOKUP_EXISTING_FLAG) { - return false; - } - break; - } - - if (norm.forbiddenSet?.[childTag] === LOOKUP_EXISTING_FLAG) { - return false; - } - - return checkContextRestrictions(childTag, ancestorInfo); -} - const CONTEXT_RESTRICTIONS: Record = Object.freeze({ form: "formTag", a: "aTagInScope", @@ -315,24 +280,56 @@ const CONTEXT_RESTRICTIONS: Record = Object.freeze({ dt: "dlItemTagAutoclosing", }); -function checkContextRestrictions( - childTag: string, - ancestorInfo: AncestorInfo -): boolean { - const k = CONTEXT_RESTRICTIONS[childTag]; - return k ? ancestorInfo[k] == null : true; -} - +/** + * Основной hot-path: одна функция, минимум вложенных вызовов. + */ export function validateDOMNesting( childTag: string, parentTag: string | null, - ancestorInfo: AncestorInfo + ancestorInfo: AncestorInfo, ): boolean { - if (!parentTag) { + if (parentTag == null) { return true; } - return isValidChild(parentTag, childTag, ancestorInfo); + // void-элементы никогда не имеют детей + if (isVoid(parentTag)) { + return false; + } + + const norm = NORMALIZED_RULES[parentTag]; + + if (norm) { + // 1) Проверка по типу разрешённого контента + switch (norm.kind) { + case AllowedKind.Any: + break; + + case AllowedKind.Phrasing: + if (!isPhrasing(childTag)) { + return false; + } + break; + + case AllowedKind.Set: { + const allowed = norm.allowedSet; + if (!allowed || allowed[childTag] !== LOOKUP_EXISTING_FLAG) { + return false; + } + break; + } + } + + // 2) Запрещённый набор (если есть) + const forbidden = norm.forbiddenSet; + if (forbidden && forbidden[childTag] === LOOKUP_EXISTING_FLAG) { + return false; + } + } + + // 3) Контекстные ограничения (формы, вложенные
, и т.д.) + const ctxKey = CONTEXT_RESTRICTIONS[childTag]; + return ctxKey ? ancestorInfo[ctxKey] == null : true; } const SCOPE_UPDATES: Record = Object.freeze({ @@ -345,11 +342,15 @@ const SCOPE_UPDATES: Record = Object.freeze({ dt: "dlItemTagAutoclosing", }); +/** + * Второй hot-path: обновление AncestorInfo максимально дёшево. + * Объект реиспользуется, без лишних аллокаций. + */ export function updateAncestorInfo( info: AncestorInfo | null, - tag: string + tag: string, ): AncestorInfo { - const ancestorInfo: AncestorInfo = info || { + const ancestorInfo: AncestorInfo = info ?? { currentTag: null, formTag: null, aTagInScope: null, @@ -360,8 +361,8 @@ export function updateAncestorInfo( }; ancestorInfo.currentTag = tag; - const scopeKey = SCOPE_UPDATES[tag]; + const scopeKey = SCOPE_UPDATES[tag]; if (scopeKey) { ancestorInfo[scopeKey] = tag; } @@ -376,12 +377,13 @@ export { IMPLIED_END_TAGS, }; +/** Внешние хелперы тоже переводим на прямой lookup, без `in`. */ export function isPhrasingContent(tagName: string): boolean { - return tagName in PHRASING_LOOKUP; + return PHRASING_LOOKUP[tagName] === LOOKUP_EXISTING_FLAG; } export function isVoidElement(tagName: string): boolean { - return tagName in VOID_LOOKUP; + return VOID_LOOKUP[tagName] === LOOKUP_EXISTING_FLAG; } export const __INTERNAL_LOOKUPS__ = { diff --git a/packages/reflex-dom/src/common/validate/DOMResourceValidation.ts b/packages/reflex-dom/src/shared/validate/DOMResourceValidation.ts similarity index 89% rename from packages/reflex-dom/src/common/validate/DOMResourceValidation.ts rename to packages/reflex-dom/src/shared/validate/DOMResourceValidation.ts index db314a0..c1c5151 100644 --- a/packages/reflex-dom/src/common/validate/DOMResourceValidation.ts +++ b/packages/reflex-dom/src/shared/validate/DOMResourceValidation.ts @@ -6,7 +6,7 @@ */ export function describeValue(value: T): string { if (value == null) { - return value === null ? "`null`" : "`undefined`"; + return value === null ? "`null`" : "`undefined`" } const type = typeof value; @@ -15,7 +15,7 @@ export function describeValue(value: T): string { const str = value as string; if (str.length === 0) { - return "`an empty string`"; + return "`an empty string`" } if (str.length < 50) { return `"${str}"`; @@ -25,7 +25,7 @@ export function describeValue(value: T): string { if (type === "number") { if (Number.isNaN(value)) { - return "`NaN`"; + return "`NaN`" } if (!Number.isFinite(value)) { return `\`${String(value)}\``; @@ -44,7 +44,7 @@ export function describeValue(value: T): string { if (value instanceof Date) { return `a Date object (${value.toISOString()})`; } - return "an object"; + return "an object" } if (type === "function") { diff --git a/packages/reflex-dom/src/common/validate/README.md b/packages/reflex-dom/src/shared/validate/README.md similarity index 100% rename from packages/reflex-dom/src/common/validate/README.md rename to packages/reflex-dom/src/shared/validate/README.md diff --git a/packages/reflex-dom/src/shared/validate/isAttributeNameSafe.ts b/packages/reflex-dom/src/shared/validate/isAttributeNameSafe.ts new file mode 100644 index 0000000..74b448a --- /dev/null +++ b/packages/reflex-dom/src/shared/validate/isAttributeNameSafe.ts @@ -0,0 +1,57 @@ +/** + * TABLE[c] = bitmask: + * bit0 (1) → valid as first char + * bit1 (2) → valid as subsequent char + */ +export const TABLE = new Uint8Array(256); + +(() => { + // First char: A–Z, a–z, _, : + for (let c = 65; c <= 90; c++) TABLE[c] = 3; // A-Z → 0b11 + for (let c = 97; c <= 122; c++) TABLE[c] = 3; // a-z → 0b11 + TABLE[95] = 3; // _ + TABLE[58] = 3; // : + + // Subsequent chars only: 0–9, -, ., · + for (let c = 48; c <= 57; c++) TABLE[c] = 2; // 0-9 → 0b10 + TABLE[45] = 2; // - + TABLE[46] = 2; // . + TABLE[183] = 2; // · (middle dot) +})(); + +// extend "both" (bitmask |= 2) for symbols allowed both first & next +// Already done for A-Z, a-z, _, : + +/** + * Validates whether an attribute name is safe according to the specified rules: + * - Start character: A-Z, a-z, _, or : + * - Name characters: Start characters plus 0-9, -, ., or \u00B7 (middle dot) + * - Ensures maximal safety and performance for library usage. + * + * @param attributeName The attribute name to validate + * @returns True if the attribute name is safe, false otherwise + */ +/** + * Lookup tables for ASCII characters. + * 1 = allowed, 0 = forbidden. + * Length is exactly 256. + */ +/** + * Ultra-fast ASCII attribute validator using two lookup tables. + */ +export function isAttributeNameSafeBranchless(name: string): boolean { + const len = name.length; + if (len === 0 || len > 256) return false; + + // First char must satisfy (TABLE[c] & 1) !== 0 + let c = name.charCodeAt(0); + if (c >= 256 || (TABLE[c]! & 1) === 0) return false; + + // Next chars: (TABLE[c] & 2) !== 0 + for (let i = 1; i < len; i++) { + c = name.charCodeAt(i); + if (c >= 256 || (TABLE[c]! & 2) === 0) return false; + } + + return true; +} diff --git a/packages/reflex-dom/tsconfig.json b/packages/reflex-dom/tsconfig.json index 679fd5d..67ea32c 100644 --- a/packages/reflex-dom/tsconfig.json +++ b/packages/reflex-dom/tsconfig.json @@ -1,9 +1,26 @@ { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src" + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "useUnknownInCatchVariables": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "allowImportingTsExtensions": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "composite": true }, + // "references": [ + // { "path": "../ref"} + // ], "include": ["src"], - "references": [{ "path": "../reflex" }] + "exclude": ["dist", "**/*.test.ts"] } diff --git a/packages/reflex/.editorconfig b/packages/reflex/.editorconfig deleted file mode 100644 index 003897b..0000000 --- a/packages/reflex/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -end_of_line = lf -charset = utf-8 -insert_final_newline = true -trim_trailing_whitespace = true - -[{*.js,*.mjs,*.ts,*.json,*.yml}] -indent_size = 2 -indent_style = space \ No newline at end of file diff --git a/packages/reflex/.prettierignore b/packages/reflex/.prettierignore deleted file mode 100644 index 84784ea..0000000 --- a/packages/reflex/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -*.log -.DS_Store -CHANGELOG.md \ No newline at end of file diff --git a/packages/reflex/AUTHORS b/packages/reflex/AUTHORS deleted file mode 100644 index 5bb4500..0000000 --- a/packages/reflex/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Andrii Volynets \ No newline at end of file diff --git a/packages/reflex/jest.config.js b/packages/reflex/jest.config.js deleted file mode 100644 index d26eab2..0000000 --- a/packages/reflex/jest.config.js +++ /dev/null @@ -1,14 +0,0 @@ -const { createDefaultPreset } = require("ts-jest"); - -const tsJestTransformCfg = createDefaultPreset().transform; - -/** @type {import("jest").Config} **/ -module.exports = { - preset: "ts-jest", - testEnvironment: "node", - transform: { - ...tsJestTransformCfg, - }, - testMatch: ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"], - moduleFileExtensions: ["ts", "js", "json"], -}; diff --git a/packages/reflex/package.json b/packages/reflex/package.json index 67432c5..a07c7f7 100644 --- a/packages/reflex/package.json +++ b/packages/reflex/package.json @@ -1,18 +1,24 @@ { - "name": "@scope/reflex", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], + "name": "reflex", + "version": "0.1.0", + "type": "module", + "description": "Reactive runtime and core API", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": ["dist"], "scripts": { - "build": "tsc --build", - "test": "jest" + "build": "tsc -p tsconfig.build.json", + "dev": "tsc -w -p tsconfig.build.json" }, - "devDependencies": { - "@types/jest": "^30.0.0", - "jest": "^30.1.3", - "ts-jest": "^29.4.4", - "typescript": "^5.9.2" + "dependencies": { + "@reflex/core": "workspace:*", + "@reflex/runtime": "workspace:*" } } diff --git a/packages/reflex/src/core/node.type.ts b/packages/reflex/src/core/node.type.ts deleted file mode 100644 index 8e3c4f4..0000000 --- a/packages/reflex/src/core/node.type.ts +++ /dev/null @@ -1,2 +0,0 @@ - -// G = {v, e} diff --git a/packages/reflex/src/core/object/inherit.test.ts b/packages/reflex/src/core/object/inherit.test.ts deleted file mode 100644 index 52b8248..0000000 --- a/packages/reflex/src/core/object/inherit.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ReflexObject } from "./inherit"; - -describe("ReflexObject.Inherit", () => { - it("should inherit methods and allow callSuper", () => { - const proto = { - value: 1, - increment(n: number) { - this.value += n; - return this.value; - }, - }; - - const obj = ReflexObject.Inherit(proto); - - expect(obj.value).toBe(1); - const result = obj.callSuper("increment", 5); - expect(result).toBe(6); - expect(obj.value).toBe(6); - }); - - it("should throw if no prototype for callSuper", () => { - const obj = ReflexObject.Inherit(null); - //@ts-ignore - testing runtime behavior - expect(() => obj.callSuper("anyMethod")).toThrow( - "[ReflexObject]: No prototype to call super on" - ); - }); - - it("should throw if key is not a function on prototype", () => { - const proto = { foo: 123 }; - const obj = ReflexObject.Inherit(proto); - expect(() => obj.callSuper("foo")).toThrow(/No method "foo" on prototype/); - }); -}); diff --git a/packages/reflex/src/core/object/inherit.ts b/packages/reflex/src/core/object/inherit.ts deleted file mode 100644 index 93b4f01..0000000 --- a/packages/reflex/src/core/object/inherit.ts +++ /dev/null @@ -1,49 +0,0 @@ -export namespace ReflexObject { - export interface SuperCaller { - callSuper( - key: K, - ...args: T[K] extends (...a: infer P) => any ? P : any - ): T[K] extends (...a: any[]) => infer R ? R : any; - } - - function __universalCallSuper( - this: T & { __protoTarget?: object }, - key: keyof T, - ...args: any[] - ): any { - const proto = this.__protoTarget; - - if (!proto) { - throw new Error("[ReflexObject]: No prototype to call super on"); - } - - const method = (proto as any)[key]; - if (typeof method !== "function") { - throw new Error( - `[ReflexObject]: No method "${String(key)}" on prototype` - ); - } - - return method.apply(this, args); - } - - export function Inherit( - proto: T | null = null - ): T & SuperCaller { - const obj = Object.create(proto) as T & - SuperCaller & { - __protoTarget?: object; - }; - - obj.__protoTarget = proto ?? undefined; - - Object.defineProperty(obj, "callSuper", { - value: __universalCallSuper, - writable: false, - enumerable: false, - configurable: false, - }); - - return obj; - } -} diff --git a/packages/reflex/src/core/ownership/ownership.context.ts b/packages/reflex/src/core/ownership/ownership.context.ts deleted file mode 100644 index 56b142f..0000000 --- a/packages/reflex/src/core/ownership/ownership.context.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { IOwnership, IOwnershipContext } from "./ownership.type"; - -/** - * Creates a new ownership context. - * Each context has a unique symbol and optional default value. - * - * @template T - Type of the context value. - * @param defaultValue - Optional default value for the context. - * @param description - Optional description for debugging purposes. - * @returns A new IOwnershipContext instance. - */ -export const createContext = ( - defaultValue?: T, - description?: string -): IOwnershipContext => ({ - id: Symbol(description), - defaultValue, -}); - -/** - * Checks if a given owner has a value for the specified context. - * - * @template T - Type of the context value. - * @param context - The context to check for. - * @param owner - Optional owner to check against. Defaults to undefined. - * @returns True if the owner has a value for the context, false otherwise. - */ -export const hasContext = ( - context: IOwnershipContext, - owner?: IOwnership -): boolean => !!owner?._context?.[context.id]; - -/** - * Retrieves the value of a context from an owner. - * - * If the owner does not have the context set, the default value is returned. - * - * @template T - Type of the context value. - * @param context - The context to retrieve. - * @param owner - Optional owner to get the context from. Defaults to undefined. - * @returns The context value or its default. - */ -export const getContext = ( - context: IOwnershipContext, - owner?: IOwnership -): T | undefined => - (owner?._context?.[context.id] as T | undefined) ?? context.defaultValue; - -/** - * Sets the value of a context on a specific owner. - * - * Creates a new prototype-based _context object if it doesn't exist or is the prototype itself, - * allowing safe shadowing for child owners without affecting parent owners. - * - * @template T - Type of the context value. - * @param context - The context to set. - * @param value - The value to assign to the context. - * @param owner - The owner on which to set the context value. - */ -export const setContext = ( - context: IOwnershipContext, - value: T, - owner?: IOwnership -) => { - if (!owner) return; - - // Ensure prototype-based inheritance for child owners - if (!owner._context || !Object.getPrototypeOf(owner._context)) { - owner._context = Object.create(owner._context ?? null); - } - - // Assign the context value - (owner._context ??= {})[context.id] = value; -}; diff --git a/packages/reflex/src/core/ownership/ownership.core.ts b/packages/reflex/src/core/ownership/ownership.core.ts deleted file mode 100644 index 59f2043..0000000 --- a/packages/reflex/src/core/ownership/ownership.core.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @file ownership.optimized.ts - * Optimized Ownership System - Zero overhead hierarchical resource management - */ - -import { ReflexObject } from "../object/inherit"; -import { batchDisposer, DisposalStrategy } from "./ownership.dispose"; -import OwnershipDisposeError from "./ownership.error"; -import { - IOwnership, - IOwnershipMethods, - OwnershipStateFlags, -} from "./ownership.type"; - -const DISPOSAL_INITIAL_CAPACITY = 4; - -/** - * Shared prototype for all Owner nodes. - */ -const OwnershipPrototype: IOwnershipMethods = { - appendChild(this: IOwnership, child: IOwnership) { - // Fast path: already attached - if (child._parent === this) return; - - // Check child state - if (child._state & OwnershipStateFlags.DISPOSED) { - throw new Error("Cannot append a disposed child"); - } - - // Check parent state - if (this._state & OwnershipStateFlags.DISPOSING) { - throw new Error("Cannot append child to an owner that is disposing"); - } - - // Detach from previous parent if needed - if (child._parent) { - child._parent.removeChild(child); - } - - // Update pointers - child._parent = this; - child._prevSibling = this._lastChild; - child._nextSibling = undefined; - - if (this._lastChild) { - this._lastChild._nextSibling = child; - this._lastChild = child; - } else { - this._firstChild = this._lastChild = child; - } - - // Inherit context only if parent has one (avoid unnecessary object creation) - if (this._context !== undefined) { - child._context = ReflexObject.Inherit(this._context); - } - - ++this._childCount; - }, - - removeChild(this: IOwnership, child: IOwnership) { - if (child._parent !== this) return; - - const prev = child._prevSibling; - const next = child._nextSibling; - - // Update sibling links - if (prev) prev._nextSibling = next; - if (next) next._prevSibling = prev; - - // Update parent links - if (this._firstChild === child) this._firstChild = next; - if (this._lastChild === child) this._lastChild = prev; - - // Clear child references - child._parent = child._prevSibling = child._nextSibling = undefined; - - --this._childCount; - }, - - onScopeMount: undefined, - - onScopeCleanup(this: IOwnership, fn: NoneToVoidFn) { - if (this._state & OwnershipStateFlags.DISPOSED) { - throw new OwnershipDisposeError(["Cannot add cleanup to disposed owner"]); - } - - if (!this._disposal) { - this._disposal = new Array(2); // Smaller initial size - this._disposal.length = 0; - } - - this._disposal.push(fn); - }, - - dispose(this: IOwnership, strategy?: DisposalStrategy) { - if (this._state & OwnershipStateFlags.DISPOSED) return; - - const batch: IOwnership[] = []; - const stack: IOwnership[] = [this]; - - while (stack.length) { - const node = stack.pop()!; - if (node._state & OwnershipStateFlags.DISPOSED) continue; - - batch.push(node); - - let child = node._firstChild; - while (child) { - stack.push(child); - child = child._nextSibling; - } - } - - batchDisposer(batch, strategy); - }, -}; - -/** - * Optimized owner creation with pre-sized disposal array - */ -function createOwner(parent?: IOwnership): IOwnership { - const owner = ReflexObject.Inherit( - OwnershipPrototype as IOwnership - ); - - // Initialize with stable hidden class - owner._parent = undefined; - owner._firstChild = undefined; - owner._lastChild = undefined; - owner._nextSibling = undefined; - owner._prevSibling = undefined; - owner._disposal = new Array(DISPOSAL_INITIAL_CAPACITY); // Pre-allocate - owner._disposal.length = 0; // But keep length at 0 - owner._context = undefined; // Will be set by appendChild if needed - owner._state = OwnershipStateFlags.CLEAN | 0; - owner._childCount = 0; - - // Attach to parent and inherit context - if (parent) { - parent.appendChild(owner); - - // Inline mount callback check - if (parent.onScopeMount) { - parent.onScopeMount(owner); - } - } - - return owner; -} - -export { IOwnership, OwnershipPrototype, createOwner }; diff --git a/packages/reflex/src/core/ownership/ownership.dispose.ts b/packages/reflex/src/core/ownership/ownership.dispose.ts deleted file mode 100644 index 73946d4..0000000 --- a/packages/reflex/src/core/ownership/ownership.dispose.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { IOwnership, OwnershipStateFlags } from "./ownership.type"; - -export interface DisposalStrategy { - onError?: (err: unknown, node: IOwnership) => void; - beforeDispose?: (nodes: IOwnership[]) => void; - afterDispose?: (nodes: IOwnership[], errors: number) => void; -} - -export function batchDisposer( - nodes: IOwnership[], - strategy?: DisposalStrategy -) { - strategy?.beforeDispose?.(nodes); - - let firstError: unknown; - let errorCount = 0; - - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - - if (node._state & OwnershipStateFlags.DISPOSED) continue; - node._state |= OwnershipStateFlags.DISPOSING; - - const disposal = node._disposal; - - for (let j = disposal.length - 1; j >= 0; j--) { - try { - disposal[j](); - } catch (err) { - if (!firstError) firstError = err; - errorCount++; - strategy?.onError?.(err, node); - } - } - - node._disposal.length = 0; - node._firstChild = node._lastChild = undefined; - node._nextSibling = node._prevSibling = undefined; - node._parent = undefined; - node._context = undefined; - node._childCount = 0; - node._state = OwnershipStateFlags.DISPOSED; - } - - strategy?.afterDispose?.(nodes, errorCount); - - if (errorCount > 0 && !strategy?.onError) { - console.error( - errorCount === 1 - ? "Error during ownership dispose:" - : `${errorCount} errors during ownership dispose. First error:`, - firstError - ); - } -} diff --git a/packages/reflex/src/core/ownership/ownership.error.ts b/packages/reflex/src/core/ownership/ownership.error.ts deleted file mode 100644 index e282671..0000000 --- a/packages/reflex/src/core/ownership/ownership.error.ts +++ /dev/null @@ -1,28 +0,0 @@ -const OWNERSHIP_ERROR_NAME = "OwnershipDisposeError"; -const OWNERSHIP_ERROR_IDENTIFIER = "[Ownership dispose]"; - -class OwnershipDisposeError extends Error { - public readonly errors: Error[]; - - constructor(errors: unknown[]) { - super( - `${OWNERSHIP_ERROR_IDENTIFIER} ${errors.length} error(s) during cleanup` - ); - this.name = OWNERSHIP_ERROR_NAME; - this.errors = errors.map((err) => - err instanceof Error ? err : new Error(String(err)) - ); - } - - toString() { - return ( - `${this.message}\n` + - this.errors - .map((e, i) => ` [${i + 1}] ${e.stack ?? e.message}`) - .join("\n") - ); - } -} - -export { OWNERSHIP_ERROR_IDENTIFIER, OWNERSHIP_ERROR_NAME }; -export default OwnershipDisposeError; diff --git a/packages/reflex/src/core/ownership/ownership.scope.ts b/packages/reflex/src/core/ownership/ownership.scope.ts deleted file mode 100644 index 6d20320..0000000 --- a/packages/reflex/src/core/ownership/ownership.scope.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { createOwner } from "./ownership.core"; -import { IOwnership } from "./ownership.type"; - -/** - * Lightweight ownership scope manager - */ -export class OwnershipScope { - private _owner?: IOwnership; - - get owner(): IOwnership | undefined { - return this._owner; - } - - run(owner: IOwnership, fn: () => T): T { - const prev = this._owner; - this._owner = owner; - - try { - return fn(); - } finally { - this._owner = prev; - } - } - - createScope(fn: () => T, parent?: IOwnership): T { - const owner = createOwner(parent ?? this._owner); - - return this.run(owner, fn); - } - - withOwner(owner: IOwnership, fn: () => T): T { - const prev = this._owner; - this._owner = owner; - - try { - return fn(); - } finally { - this._owner = prev; - } - } -} diff --git a/packages/reflex/src/core/ownership/ownership.spec.ts b/packages/reflex/src/core/ownership/ownership.spec.ts deleted file mode 100644 index 8115aa5..0000000 --- a/packages/reflex/src/core/ownership/ownership.spec.ts +++ /dev/null @@ -1,709 +0,0 @@ -import { ReflexObject } from "../object/inherit"; -import { OwnershipPrototype, createOwner } from "./ownership.core"; -import { IOwnership, OwnershipStateFlags } from "./ownership.type"; -import { OwnershipScope } from "./ownership.scope"; - -describe("Ownership System - Core Functionality", () => { - let scope: OwnershipScope; - - beforeEach(() => { - scope = new OwnershipScope(); - }); - - describe("Scope Management", () => { - test("create() creates new owner and restores previous owner", () => { - let capturedOwner: IOwnership | undefined; - - const result = scope.createScope(() => { - capturedOwner = scope.owner; - return 42; - }); - - expect(result).toBe(42); - expect(capturedOwner).toBeDefined(); - expect(capturedOwner!._state).toBe(OwnershipStateFlags.CLEAN); - expect(scope.owner).toBeUndefined(); - }); - - test("nested scope creation stress test", () => { - const NESTING_LEVELS = 100; - let deepestOwner: IOwnership | undefined; - - scope.createScope(() => { - let currentLevel = 0; - let currentParent = scope.owner; - - const nest = () => { - if (currentLevel++ < NESTING_LEVELS) { - scope.createScope(() => { - currentParent = scope.owner; - nest(); - }, currentParent); - } else { - deepestOwner = scope.owner; - } - }; - - nest(); - }); - - expect(deepestOwner).toBeDefined(); - - let depth = 0; - let current = deepestOwner; - while (current) { - depth++; - current = current._parent; - } - - expect(depth).toBe(NESTING_LEVELS + 1); - }); - - test("run() temporarily sets owner and restores", () => { - const owner = createOwner(); - let capturedOwner: IOwnership | undefined; - - scope.run(owner, () => { - capturedOwner = scope.owner; - }); - - expect(capturedOwner).toBe(owner); - expect(scope.owner).toBeUndefined(); - }); - - test("nested scopes maintain correct owner chain with mixed parent assignment", () => { - const scope = new OwnershipScope(); - const owners: IOwnership[] = []; - - // Level 0 - scope.createScope(() => { - owners.push(scope.owner!); // owners[0] - - // Level 1, auto parent (owners[0]) - scope.createScope(() => { - owners.push(scope.owner!); // owners[1] - - // Level 2, explicit parent = owners[1] - scope.createScope(() => { - owners.push(scope.owner!); // owners[2] - }, owners[1]); - - // Level 2, auto parent (owners[1]) - scope.createScope(() => { - owners.push(scope.owner!); // owners[3] - - // Level 3, explicit parent = owners[3] - scope.createScope(() => { - owners.push(scope.owner!); // owners[4] - }, owners[3]); - }); - - // Level 2, explicit parent = owners[1] - scope.createScope(() => { - owners.push(scope.owner!); // owners[5] - }, owners[1]); - }); - }); - - // Проверяем количество - expect(owners).toHaveLength(6); - - // Проверка цепочек - expect(owners[0]._firstChild).toBe(owners[1]); - expect(owners[1]._parent).toBe(owners[0]); - - // owners[1] children: owners[2], owners[3], owners[5] - expect(owners[1]._firstChild).toBe(owners[2]); - expect(owners[2]._parent).toBe(owners[1]); - expect(owners[2]._nextSibling).toBe(owners[3]); - expect(owners[3]._prevSibling).toBe(owners[2]); - expect(owners[3]._nextSibling).toBe(owners[5]); - expect(owners[5]._prevSibling).toBe(owners[3]); - expect(owners[5]._parent).toBe(owners[1]); - expect(owners[1]._lastChild).toBe(owners[5]); - - // owners[3] child: owners[4] - expect(owners[3]._firstChild).toBe(owners[4]); - expect(owners[4]._parent).toBe(owners[3]); - expect(owners[3]._lastChild).toBe(owners[4]); - - // owners[0] last child - expect(owners[0]._lastChild).toBe(owners[1]); - }); - }); - - describe("Tree Structure", () => { - test("appendChild links children correctly", () => { - const parent = createOwner(); - const child1 = createOwner(); - const child2 = createOwner(); - const child3 = createOwner(); - - parent.appendChild(child1); - parent.appendChild(child2); - parent.appendChild(child3); - - expect(parent._firstChild).toBe(child1); - expect(parent._lastChild).toBe(child3); - expect(parent._childCount).toBe(3); - - expect(child1._parent).toBe(parent); - expect(child1._prevSibling).toBeUndefined(); - expect(child1._nextSibling).toBe(child2); - - expect(child2._parent).toBe(parent); - expect(child2._prevSibling).toBe(child1); - expect(child2._nextSibling).toBe(child3); - - expect(child3._parent).toBe(parent); - expect(child3._prevSibling).toBe(child2); - expect(child3._nextSibling).toBeUndefined(); - }); - - test("removeChild unlinks child correctly", () => { - const parent = createOwner(); - const children = [createOwner(), createOwner(), createOwner()]; - - children.forEach((c) => parent.appendChild(c)); - - // Remove middle child - parent.removeChild(children[1]); - - expect(parent._childCount).toBe(2); - expect(children[0]._nextSibling).toBe(children[2]); - expect(children[2]._prevSibling).toBe(children[0]); - expect(children[1]._parent).toBeUndefined(); - expect(children[1]._prevSibling).toBeUndefined(); - expect(children[1]._nextSibling).toBeUndefined(); - }); - - test("removeChild handles first and last child", () => { - const parent = createOwner(); - const children = [createOwner(), createOwner(), createOwner()]; - - children.forEach((c) => parent.appendChild(c)); - - // Remove first - parent.removeChild(children[0]); - expect(parent._firstChild).toBe(children[1]); - expect(parent._childCount).toBe(2); - - // Remove last - parent.removeChild(children[2]); - expect(parent._lastChild).toBe(children[1]); - expect(parent._childCount).toBe(1); - }); - - test("appendChild detaches from previous parent", () => { - const parent1 = createOwner(); - const parent2 = createOwner(); - const child = createOwner(); - - parent1.appendChild(child); - expect(parent1._childCount).toBe(1); - expect(child._parent).toBe(parent1); - - parent2.appendChild(child); - expect(parent1._childCount).toBe(0); - expect(parent2._childCount).toBe(1); - expect(child._parent).toBe(parent2); - }); - - test("appendChild is idempotent for same parent", () => { - const parent = createOwner(); - const child = createOwner(); - - parent.appendChild(child); - const initialCount = parent._childCount; - - parent.appendChild(child); - - expect(parent._childCount).toBe(initialCount); - expect(parent._firstChild).toBe(child); - expect(parent._lastChild).toBe(child); - }); - - test("createOwner with parent auto-appends", () => { - const parent = createOwner(); - const child = createOwner(parent); - - expect(parent._firstChild).toBe(child); - expect(parent._childCount).toBe(1); - expect(child._parent).toBe(parent); - }); - }); - - describe("Context Inheritance", () => { - test("child inherits parent context via prototype chain", () => { - const parent = createOwner(); - parent._context = { foo: "bar", nested: { value: 42 } }; - - const child = createOwner(parent); - - expect(child._context).not.toBe(parent._context); - expect(child._context!.foo).toBe("bar"); - expect( - (child._context as { nested: { value: number } }).nested.value - ).toBe(42); - }); - - test("child context modifications don't affect parent", () => { - const parent = createOwner(); - parent._context = { foo: "bar" }; - - const child = createOwner(parent); - child._context!.foo = "baz"; - child._context!.newProp = "new"; - - expect(parent._context!.foo).toBe("bar"); - expect(parent._context!.newProp).toBeUndefined(); - }); - - test("context is not created if parent has no context", () => { - const parent = createOwner(); - const child = createOwner(parent); - - expect(child._context).toBeUndefined(); - }); - }); - - describe("Cleanup & Disposal", () => { - test("onScopeCleanup registers and executes callbacks", () => { - const owner = createOwner(); - const callbacks = [jest.fn(), jest.fn(), jest.fn()]; - - callbacks.forEach((cb) => owner.onScopeCleanup(cb)); - owner.dispose(); - - callbacks.forEach((cb) => expect(cb).toHaveBeenCalledTimes(1)); - expect(owner._disposal).toEqual([]); - expect(owner._state).toBe(OwnershipStateFlags.DISPOSED); - }); - - test("disposal executes callbacks in registration order", () => { - const owner = createOwner(); - const order: number[] = []; - - owner.onScopeCleanup(() => order.push(1)); - owner.onScopeCleanup(() => order.push(2)); - owner.onScopeCleanup(() => order.push(3)); - - owner.dispose(); - - expect(order).toEqual([3, 2, 1]); - }); - - test("deep tree disposal disposes all descendants", () => { - const root = createOwner(); - const level1 = [createOwner(root), createOwner(root)]; - const level2 = [ - createOwner(level1[0]), - createOwner(level1[0]), - createOwner(level1[1]), - ]; - - const allNodes = [root, ...level1, ...level2]; - const callbacks = allNodes.map(() => jest.fn()); - - allNodes.forEach((node, i) => node.onScopeCleanup(callbacks[i])); - - root.dispose(); - - callbacks.forEach((cb) => expect(cb).toHaveBeenCalledTimes(1)); - allNodes.forEach((node) => { - expect(node._state).toBe(OwnershipStateFlags.DISPOSED); - expect(node._firstChild).toBeUndefined(); - expect(node._lastChild).toBeUndefined(); - expect(node._childCount).toBe(0); - }); - }); - - test("dispose is idempotent", () => { - const owner = createOwner(); - const callback = jest.fn(); - - owner.onScopeCleanup(callback); - - owner.dispose(); - owner.dispose(); - owner.dispose(); - - expect(callback).toHaveBeenCalledTimes(1); - expect(owner._state).toBe(OwnershipStateFlags.DISPOSED); - }); - - // test("errors in cleanup don't prevent other cleanups", () => { - // const owner = createOwner(); - // const callbacks = [ - // jest.fn(), - // jest.fn(() => { throw new Error("Error 1"); }), - // jest.fn(), - // jest.fn(() => { throw new Error("Error 2"); }), - // jest.fn() - // ]; - - // callbacks.forEach(cb => owner.onScopeCleanup(cb)); - - // const consoleSpy = jest.spyOn(console, "error").mockImplementation(); - - // owner.dispose(); - - // consoleSpy.mockRestore(); - - // callbacks.forEach(cb => expect(cb).toHaveBeenCalledTimes(1)); - // expect(owner._state).toBe(OwnershipStateFlags.DISPOSED); - // expect(consoleSpy).toHaveBeenCalledTimes(1); - // }); - - test("cleanup with circular references doesn't cause issues", () => { - const owner = createOwner(); - const obj: any = { value: 42 }; - obj.self = obj; - - owner.onScopeCleanup(() => { - obj.value = 0; - }); - - expect(() => owner.dispose()).not.toThrow(); - expect(obj.value).toBe(0); - }); - }); - - describe("Safety & Edge Cases", () => { - test("cannot append disposed child", () => { - const parent = createOwner(); - const child = createOwner(); - - child.dispose(); - - expect(() => parent.appendChild(child)).toThrow( - "Cannot append a disposed child" - ); - }); - - test("cannot append child to disposing owner", () => { - const parent = createOwner(); - const child = createOwner(); - let errorThrown = false; - - parent.onScopeCleanup(() => { - try { - parent.appendChild(child); - } catch (e: any) { - errorThrown = - e.message === "Cannot append child to an owner that is disposing"; - } - }); - - parent.dispose(); - expect(errorThrown).toBe(true); - }); - - test("removeChild with non-child does nothing", () => { - const parent = createOwner(); - const notChild = createOwner(); - - expect(() => parent.removeChild(notChild)).not.toThrow(); - expect(parent._childCount).toBe(0); - }); - - test("disposal clears all references", () => { - const parent = createOwner(); - const child = createOwner(parent); - - parent._context = { data: "test" }; - child.onScopeCleanup(() => {}); - - parent.dispose(); - - expect(parent._firstChild).toBeUndefined(); - expect(parent._lastChild).toBeUndefined(); - expect(parent._context).toBeUndefined(); - expect(parent._disposal.length).toBe(0); - expect(child._parent).toBeUndefined(); - expect(child._nextSibling).toBeUndefined(); - expect(child._prevSibling).toBeUndefined(); - }); - }); -}); - -describe("Ownership System - Performance & Stress Tests", () => { - describe("Memory Efficiency", () => { - test("disposal should release memory references", () => { - const root = createOwner(); - - // Create and dispose tree - for (let i = 0; i < 100; i++) { - createOwner(root); - } - - expect(root._childCount).toBe(100); - - root.dispose(); - - expect(root._firstChild).toBeUndefined(); - expect(root._childCount).toBe(0); - }); - - test("pre-allocated disposal array reduces allocations", () => { - const owner = createOwner(); - - expect(Array.isArray(owner._disposal)).toBe(true); - - for (let i = 0; i < 10; i++) { - owner.onScopeCleanup(() => {}); - } - - expect(owner._disposal.length).toBe(10); - }); - }); - - describe("Deep Tree Performance", () => { - const BALANCED_DEPTH = 8; - const BALANCED_CHILDREN = 3; - - function buildBalancedTree(depth: number, parent?: IOwnership): IOwnership { - const node = createOwner(parent); - - if (depth > 0) { - for (let i = 0; i < BALANCED_CHILDREN; i++) { - buildBalancedTree(depth - 1, node); - } - } - - return node; - } - - function countNodes(node: IOwnership): number { - let count = 1; - let child = node._firstChild; - - while (child) { - count += countNodes(child); - child = child._nextSibling; - } - - return count; - } - - test("balanced tree creation and disposal", () => { - const start = performance.now(); - const root = buildBalancedTree(BALANCED_DEPTH); - const buildTime = performance.now() - start; - - const nodeCount = countNodes(root); - const expectedNodes = - (Math.pow(BALANCED_CHILDREN, BALANCED_DEPTH + 1) - 1) / - (BALANCED_CHILDREN - 1); - - expect(nodeCount).toBe(expectedNodes); - - const disposeStart = performance.now(); - root.dispose(); - const disposeTime = performance.now() - disposeStart; - - console.log(`Built ${nodeCount} nodes in ${buildTime.toFixed(2)}ms`); - console.log(`Disposed ${nodeCount} nodes in ${disposeTime.toFixed(2)}ms`); - console.log( - `Throughput: ${((nodeCount / disposeTime) * 1000).toFixed(0)} nodes/sec` - ); - - expect(root._state).toBe(OwnershipStateFlags.DISPOSED); - expect(disposeTime).toBeLessThan(100); - }); - - test("wide tree with many siblings", () => { - const root = createOwner(); - const SIBLING_COUNT = 3000; - - const start = performance.now(); - for (let i = 0; i < SIBLING_COUNT; i++) { - createOwner(root); - } - const buildTime = performance.now() - start; - - expect(root._childCount).toBe(SIBLING_COUNT); - - const disposeStart = performance.now(); - root.dispose(); - const disposeTime = performance.now() - disposeStart; - - console.log( - `Created ${SIBLING_COUNT} siblings in ${buildTime.toFixed(2)}ms` - ); - console.log( - `Disposed ${SIBLING_COUNT} siblings in ${disposeTime.toFixed(2)}ms` - ); - - expect(root._state).toBe(OwnershipStateFlags.DISPOSED); - expect(disposeTime).toBeLessThan(50); - }); - - test("deep linear chain doesn't overflow stack", () => { - const CHAIN_LENGTH = 10000; - let current = createOwner(); - const root = current; - - for (let i = 0; i < CHAIN_LENGTH; i++) { - current = createOwner(current); - } - - expect(() => root.dispose()).not.toThrow(); - expect(root._state).toBe(OwnershipStateFlags.DISPOSED); - }); - }); - - describe("Cleanup Callback Performance", () => { - test("many cleanup callbacks execute efficiently", () => { - const owner = createOwner(); - const CALLBACK_COUNT = 10000; - let counter = 0; - - for (let i = 0; i < CALLBACK_COUNT; i++) { - owner.onScopeCleanup(() => counter++); - } - - const start = performance.now(); - owner.dispose(); - const elapsed = performance.now() - start; - - expect(counter).toBe(CALLBACK_COUNT); - console.log( - `Executed ${CALLBACK_COUNT} callbacks in ${elapsed.toFixed(2)}ms` - ); - console.log( - `Throughput: ${((CALLBACK_COUNT / elapsed) * 1000).toFixed( - 0 - )} callbacks/sec` - ); - - expect(elapsed).toBeLessThan(50); - }); - - test("cleanup with complex state changes", () => { - const owner = createOwner(); - const state = { values: new Array(1000).fill(0) }; - - for (let i = 0; i < state.values.length; i++) { - owner.onScopeCleanup(() => { - state.values[i] = 1; - }); - } - - owner.dispose(); - - expect(state.values.every((v) => v === 1)).toBe(true); - }); - }); - - describe("Concurrent Operations", () => { - test("interleaved appendChild and removeChild", () => { - const parent = createOwner(); - const children: IOwnership[] = []; - - for (let i = 0; i < 100; i++) { - const child = createOwner(); - parent.appendChild(child); - children.push(child); - - if (i % 3 === 0 && children.length > 1) { - const toRemove = children.splice( - Math.floor(Math.random() * children.length), - 1 - )[0]; - parent.removeChild(toRemove); - } - } - - expect(parent._childCount).toBe(children.length); - - let count = 0; - let current = parent._firstChild; - while (current) { - count++; - current = current._nextSibling; - } - expect(count).toBe(parent._childCount); - }); - }); - - describe("Real-World Scenarios", () => { - test("component tree simulation", () => { - const app = createOwner(); - - const header = createOwner(app); - createOwner(header); - createOwner(header); - - const main = createOwner(app); - - for (let i = 0; i < 20; i++) { - const item = createOwner(main); - item.onScopeCleanup(() => {}); - } - - const footer = createOwner(app); - - expect(app._childCount).toBe(3); - expect(main._childCount).toBe(20); - - app.dispose(); - - expect(app._state).toBe(OwnershipStateFlags.DISPOSED); - }); - - test("subscription cleanup pattern", () => { - const owner = createOwner(); - const subscriptions: Set<() => void> = new Set(); - - for (let i = 0; i < 100; i++) { - const unsubscribe = jest.fn(); - subscriptions.add(unsubscribe); - owner.onScopeCleanup(unsubscribe); - } - - owner.dispose(); - - subscriptions.forEach((unsub) => { - expect(unsub).toHaveBeenCalledTimes(1); - }); - }); - - test("context-based dependency injection", () => { - const root = createOwner(); - root._context = { - services: { - api: "https://api.example.com", - auth: { token: "secret" }, - }, - }; - - const child1 = createOwner(root); - const child2 = createOwner(root); - - type ContextType = { - services: { api: string; auth?: { token: string } }; - }; - - expect((child1._context as ContextType).services.api).toBe( - "https://api.example.com" - ); - expect((child2._context as ContextType).services.api).toBe( - "https://api.example.com" - ); - - (child1._context as ContextType).services = { - ...(child1._context as ContextType).services, - api: "override", - }; - - expect((child1._context as ContextType).services.api).toBe("override"); - expect((root._context as ContextType).services.api).toBe( - "https://api.example.com" - ); - expect((child2._context as ContextType).services.api).toBe( - "https://api.example.com" - ); - }); - }); -}); diff --git a/packages/reflex/src/core/ownership/ownership.type.ts b/packages/reflex/src/core/ownership/ownership.type.ts deleted file mode 100644 index 041e819..0000000 --- a/packages/reflex/src/core/ownership/ownership.type.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @file ownership.type.ts - * Types, symbols, and flags for Ownership system. - * Provides core building blocks for reactive ownership, context, and cleanup. - */ - -/** Unique identifier for general purpose internal ID. */ -const S_ID: unique symbol = Symbol.for("id"); -/** Internal reference to the owner of a reactive node. */ -const S_OWN: unique symbol = Symbol.for("ownership"); -/** Internal source nodes for memoization/tracking dependencies. */ -const S_SOURCES: unique symbol = Symbol.for("sources"); -/** Internal subscribers for reactive nodes. */ -const S_SUBS: unique symbol = Symbol.for("subscribers"); -/** Marks a node as dirty or needing update. */ -const S_DIRTY: unique symbol = Symbol.for("dirty"); -/** Stores the function for memo/effect computation. */ -const S_FN: unique symbol = Symbol.for("fn"); -/** Holds the current value of a signal/memo. */ -const S_VALUE: unique symbol = Symbol.for("value"); -/** Registered cleanup callbacks for disposal. */ -const S_DISPOSE: unique symbol = Symbol.for("disposeCallbacks"); - -/** Type of the context object attached to an Owner. */ -type IOwnershipContextRecord = Record; - -/** - * Represents a context value that can be attached to an Owner. - * Contexts support inheritance via prototype chains. - * - * @template T - Type of the context value. - */ -type IOwnershipContext = { - /** Unique identifier for this context. */ - readonly id: symbol; - /** Default value to return if the context is missing in the owner. */ - readonly defaultValue?: T; -}; - -/** - * Bitwise flags representing the lifecycle state of an Ownership node. - * Allows fast checks via bitwise operations. - */ -const enum OwnershipStateFlags { - /** Node is clean, no pending updates. */ - CLEAN = 0, - /** Node is scheduled for validation/check. */ - CHECK = 1 << 0, - /** Node has pending updates (dirty). */ - DIRTY = 1 << 1, - /** Node is in the process of disposal. */ - DISPOSING = 1 << 2, - /** Node has been disposed and should not be reused. */ - DISPOSED = 1 << 3, -} - -/** Function type for cleanup callbacks or effect disposers. */ -type NoneToVoidFn = () => void; - -/** - * Methods shared by all Owner nodes. - * Placed on prototype for memory efficiency and stable hidden class layout. - */ -interface IOwnershipMethods { - /** - * Append a child node to this owner. - * Updates `_firstChild`, `_lastChild`, `_nextSibling`, and `_childCount`. - * Initializes child context via prototype inheritance. - * - * @param child - Child owner to attach. - */ - appendChild(child: IOwnership): void; - - /** - * Optional hook triggered when a scope is mounted. - * - * @param scope - The mounted child owner. - */ - onScopeMount?(scope: IOwnership): void; - - /** - * Register a cleanup callback to be executed during disposal. - * Callbacks are executed in registration order. Errors are caught and logged. - * - * @param fn - Cleanup function. - */ - onScopeCleanup(fn: NoneToVoidFn): void; - - /** - * Remove a direct child from this owner. - * - * @param child - Child owner to remove. - */ - removeChild(child: IOwnership): void; - - /** - * Dispose this node and all descendants. - * Iteratively traverses children to avoid recursion. - * Executes cleanup callbacks and clears references. - */ - dispose(): void; -} - -/** - * Represents a single node in the Ownership tree. - * Nodes track children, context, state, and disposal callbacks. - */ -interface IOwnership extends IOwnershipMethods { - /** Parent node in the ownership tree. */ - _parent?: IOwnership; - - /** First child node in the linked list of children. */ - _firstChild?: IOwnership; - - /** Last child node, used for O(1) append. */ - _lastChild?: IOwnership; - - /** Next sibling node in the parent's child list. */ - _nextSibling?: IOwnership; - - /** Prev sibling node in the parent's child list that makes list is linked and remove in O(1). */ - _prevSibling?: IOwnership; - - /** Array of cleanup callbacks registered via `onScopeCleanup`. */ - _disposal: NoneToVoidFn[]; - - /** Context object for scoped variables, prototypally inherited from parent. */ - _context?: Record; - - /** Bitwise state flags describing the node lifecycle. */ - _state: OwnershipStateFlags; - - /** Number of immediate children attached to this node. */ - _childCount: number; -} - -export { - OwnershipStateFlags, - S_ID, - S_OWN, - S_SOURCES, - S_SUBS, - S_DIRTY, - S_FN, - S_VALUE, - S_DISPOSE, -}; - -export type { - IOwnership, - IOwnershipMethods, - IOwnershipContextRecord, - IOwnershipContext, - NoneToVoidFn, -}; diff --git a/packages/reflex/src/core/reactivity/abstract_signal.ts b/packages/reflex/src/core/reactivity/abstract_signal.ts deleted file mode 100644 index b333528..0000000 --- a/packages/reflex/src/core/reactivity/abstract_signal.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Disposable, SignalInterface } from "./abstract_types"; - -abstract class AbstractSignal implements SignalInterface { - abstract readonly _value: T; - - get(): T { - return this._value; - } - - subscribe(observer: (value: T) => void): Disposable {} - - unsubscribe(observer: (value: T) => void): void {} - - dispose(): void { - // Implementation here - } -} - -export default AbstractSignal; diff --git a/packages/reflex/src/core/reactivity/abstract_types.ts b/packages/reflex/src/core/reactivity/abstract_types.ts deleted file mode 100644 index ac5bb48..0000000 --- a/packages/reflex/src/core/reactivity/abstract_types.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - OWNERSHIP_BRAND, - VERSION_DEFINITION, - WRITABLE_BRAND, - SET_DEFINITION, - COMPUTED_BRAND, -} from "./common_constants"; - -declare const LOCAL_DISPOSABLE_BRAND: unique symbol; -type Disposable = (() => void) & { [LOCAL_DISPOSABLE_BRAND]: true }; - -interface ISignal { - readonly [OWNERSHIP_BRAND]: true; - readonly [VERSION_DEFINITION]: number; - - get(): T; - subscribe(observer: (value: T) => void): Disposable; - dispose(): void; -} - -interface IWritableSignal extends ISignal { - readonly [WRITABLE_BRAND]: true; - [SET_DEFINITION](value: T | ((prev: T) => T)): void; -} - -interface IComputedSignal extends ISignal { - readonly [COMPUTED_BRAND]: true; -} - -/** - * An abstract class representing ownership of signals. - * This class implements the ISignal interface and provides - * default implementations for its methods. - * - * Means to mark a class as an owner of signals and track to automaticaly dispose using the `dispose` method. - * - * If you write the following - * - * const a = createOwner(() => { - * const b = createOwner(() => {}); - * - * const c = createOwner(() => { - * const d = createOwner(() => {}); - * }); - * - * const e = createOwner(() => {}); - * }); - * - * The owner tree will look like this: - * - * a - * /|\ - * b-c-e - * | - * d - * - * Following the _nextSibling pointers of each owner will first give you its children, and then its siblings (in reverse). - * a -> e -> c -> d -> b - * - */ -abstract class Ownership implements ISignal { - -} - -// class SignalError extends Error { -// constructor(message: string) { -// super(message); -// this.name = "SignalError"; -// } - -// static readonly NOT_WRITABLE = new SignalError("The signal is not writable."); -// static readonly DISPOSED = new SignalError("The signal has been disposed."); -// static readonly CIRCULAR = new SignalError( -// "A circular dependency has been detected." -// ); -// } - -export { ISignal, IWritableSignal, IComputedSignal, Disposable }; diff --git a/packages/reflex/src/core/reactivity/common_constants.ts b/packages/reflex/src/core/reactivity/common_constants.ts deleted file mode 100644 index 8a24497..0000000 --- a/packages/reflex/src/core/reactivity/common_constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const EMPTY_ARRAY = []; - -export const OWNERSHIP_BRAND: unique symbol = Symbol("ownership"); -export const COMPUTED_BRAND: unique symbol = Symbol("computed"); -export const WRITABLE_BRAND: unique symbol = Symbol("writable"); - -export const VERSION_DEFINITION = "_version"; -export const SET_DEFINITION = "set"; diff --git a/packages/reflex/src/globals.d.ts b/packages/reflex/src/globals.d.ts deleted file mode 100644 index fe5b927..0000000 --- a/packages/reflex/src/globals.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Reflex - */ - -/** - * Represents a function that takes no arguments and returns nothing. - * - * Commonly used for cleanup callbacks, disposers, unsubscribers, - * or any "fire and forget" side-effect handlers. - * - * Signature: - * ```ts - * type NoneToVoidFn = () => void; - * ``` - * - * Examples: - * ```ts - * const dispose: NoneToVoidFn = () => { - * clearTimeout(timerId); - * }; - * - * owner.onCleanup(dispose); - * ``` - * - * Mnemonic: - * - (None) → Void - * - No parameters in, nothing out. - */ -type NoneToVoidFn = () => void; diff --git a/packages/reflex/src/index.ts b/packages/reflex/src/index.ts index e69de29..d948f7e 100644 --- a/packages/reflex/src/index.ts +++ b/packages/reflex/src/index.ts @@ -0,0 +1,6 @@ +// // Main public API +// export { +// createSignal, +// createEffect, +// batch +// } from "@reflex/core"; diff --git a/packages/reflex/tsconfig.json b/packages/reflex/tsconfig.json deleted file mode 100644 index 4bd9005..0000000 --- a/packages/reflex/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "typeRoots": ["./types", "./node_modules/@types"], - "composite": true, - "outDir": "dist", - "rootDir": "src", - "types": ["jest", "node"] - } -} diff --git a/packages/tsconfig.base.json b/packages/tsconfig.base.json deleted file mode 100644 index eb5ef29..0000000 --- a/packages/tsconfig.base.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "node16", - "lib": ["DOM", "ES2022"], - "strict": true, - "moduleResolution": "node16", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "composite": true, - "incremental": true, - "baseUrl": ".", - "paths": { - "@reflex/*": ["packages/reflex/src/*"], - "@reflex-dom/*": ["packages/reflex-dom/src/*"] - } - } -} diff --git a/plugins/@eslint/Readme.md b/plugins/@eslint/Readme.md new file mode 100644 index 0000000..474ac53 --- /dev/null +++ b/plugins/@eslint/Readme.md @@ -0,0 +1,132 @@ +# @reflex/eslint-plugin-forbidden-imports + +> Prevents direct usage of internal `@reflex/*` packages outside the Reflex core. + +This ESLint plugin enforces **architectural boundaries** of the Reflex ecosystem by forbidding imports from internal packages (`@reflex/*`) in application-level code. + +It is designed as a **soft but strict guardrail** that: + +- Keeps the public API clean (`reflex`, `reflex-dom`) +- Prevents accidental coupling with internals +- Preserves architectural discipline +- Supports layered system design + +If you want advanced access – you should **know exactly why** you need it. + +--- + +## 🚫 What is forbidden? + +```ts +import { createSignal } from "@reflex/core"; // ❌ forbidden +import { createUniverse } from "@reflex/runtime"; // ❌ forbidden +import type { IOwner } from "@reflex/contract"; // ❌ forbidden +``` + +Allowed usage: + +```ts +import { createSignal } from "reflex"; // ✅ OK +import { render } from "reflex-dom"; // ✅ OK +``` + +Imports from `@reflex/*` are allowed **only** inside Reflex internal packages: + +- `packages/@reflex/**` +- `packages/reflex/**` +- `plugins/**` +- `theory/**` + +Everywhere else — blocked. + +--- + +## 📦 Installation + +From the root of your monorepo: + +```bash +pnpm add -D ./plugins/forbidden-imports +``` + +Or when published: + +```bash +pnpm add -D @reflex/eslint-plugin-forbidden-imports +``` + +--- + +## 🔧 Usage + +In your root `.eslintrc.cjs`: + +```js +module.exports = { + plugins: ["forbidden-imports"], + rules: { + "forbidden-imports/forbidden-imports": "error", + }, +}; +``` + +Now if someone writes: + +```ts +import { something } from "@reflex/core"; +``` + +They will get: + +> ❌ Internal import '@reflex/core' is forbidden here. Use 'reflex' or 'reflex-dom' instead. + +--- + +## 🧠 Why this exists + +Reflex is designed as a **layered runtime system**: + +``` +Application → reflex → @reflex/core → @reflex/runtime → @reflex/contract +``` + +Only the public surface (`reflex`, `reflex-dom`) should be used by applications. + +This plugin exists to: + +- Protect runtime invariants +- Avoid experimental APIs leaking into apps +- Keep mental models clean for new developers +- Enforce system boundaries at scale + +It is **not** about hierarchy or control. +It is about **system integrity**. + +--- + +## 🧬 Philosophy + +> In Reflex, architecture is not a suggestion. +> It is a **law of the universe**. + +This plugin is one of those laws. + +No `__DEV__`. +No build-time hacks. + +Just a clear semantic boundary — enforced. + +--- + +## 🔮 Future rules (planned) + +This plugin may later include: + +- `no-owner-mutation-inside-effect` +- `no-graph-mutation-outside-runtime` +- `no-cross-epoch-side-effects` +- `atomic-only-in-batch` +- `no-illegal-scheduler-usage` + +In other words: +**Static enforcement of the Theory of Reactivity**. diff --git a/plugins/@eslint/eslint-plugin-forbidden-imports/Readme.md b/plugins/@eslint/eslint-plugin-forbidden-imports/Readme.md new file mode 100644 index 0000000..474ac53 --- /dev/null +++ b/plugins/@eslint/eslint-plugin-forbidden-imports/Readme.md @@ -0,0 +1,132 @@ +# @reflex/eslint-plugin-forbidden-imports + +> Prevents direct usage of internal `@reflex/*` packages outside the Reflex core. + +This ESLint plugin enforces **architectural boundaries** of the Reflex ecosystem by forbidding imports from internal packages (`@reflex/*`) in application-level code. + +It is designed as a **soft but strict guardrail** that: + +- Keeps the public API clean (`reflex`, `reflex-dom`) +- Prevents accidental coupling with internals +- Preserves architectural discipline +- Supports layered system design + +If you want advanced access – you should **know exactly why** you need it. + +--- + +## 🚫 What is forbidden? + +```ts +import { createSignal } from "@reflex/core"; // ❌ forbidden +import { createUniverse } from "@reflex/runtime"; // ❌ forbidden +import type { IOwner } from "@reflex/contract"; // ❌ forbidden +``` + +Allowed usage: + +```ts +import { createSignal } from "reflex"; // ✅ OK +import { render } from "reflex-dom"; // ✅ OK +``` + +Imports from `@reflex/*` are allowed **only** inside Reflex internal packages: + +- `packages/@reflex/**` +- `packages/reflex/**` +- `plugins/**` +- `theory/**` + +Everywhere else — blocked. + +--- + +## 📦 Installation + +From the root of your monorepo: + +```bash +pnpm add -D ./plugins/forbidden-imports +``` + +Or when published: + +```bash +pnpm add -D @reflex/eslint-plugin-forbidden-imports +``` + +--- + +## 🔧 Usage + +In your root `.eslintrc.cjs`: + +```js +module.exports = { + plugins: ["forbidden-imports"], + rules: { + "forbidden-imports/forbidden-imports": "error", + }, +}; +``` + +Now if someone writes: + +```ts +import { something } from "@reflex/core"; +``` + +They will get: + +> ❌ Internal import '@reflex/core' is forbidden here. Use 'reflex' or 'reflex-dom' instead. + +--- + +## 🧠 Why this exists + +Reflex is designed as a **layered runtime system**: + +``` +Application → reflex → @reflex/core → @reflex/runtime → @reflex/contract +``` + +Only the public surface (`reflex`, `reflex-dom`) should be used by applications. + +This plugin exists to: + +- Protect runtime invariants +- Avoid experimental APIs leaking into apps +- Keep mental models clean for new developers +- Enforce system boundaries at scale + +It is **not** about hierarchy or control. +It is about **system integrity**. + +--- + +## 🧬 Philosophy + +> In Reflex, architecture is not a suggestion. +> It is a **law of the universe**. + +This plugin is one of those laws. + +No `__DEV__`. +No build-time hacks. + +Just a clear semantic boundary — enforced. + +--- + +## 🔮 Future rules (planned) + +This plugin may later include: + +- `no-owner-mutation-inside-effect` +- `no-graph-mutation-outside-runtime` +- `no-cross-epoch-side-effects` +- `atomic-only-in-batch` +- `no-illegal-scheduler-usage` + +In other words: +**Static enforcement of the Theory of Reactivity**. diff --git a/plugins/@eslint/eslint-plugin-forbidden-imports/package.json b/plugins/@eslint/eslint-plugin-forbidden-imports/package.json new file mode 100644 index 0000000..71c7bce --- /dev/null +++ b/plugins/@eslint/eslint-plugin-forbidden-imports/package.json @@ -0,0 +1,19 @@ +{ + "name": "@reflex/eslint-plugin-forbidden-imports", + "version": "0.1.0", + "description": "Prevents direct imports from @reflex/* in application code", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc" + }, + "peerDependencies": { + "eslint": "^8.0.0" + }, + "devDependencies": { + "@types/estree": "^1.0.8" + } +} diff --git a/plugins/@eslint/eslint-plugin-forbidden-imports/src/index.ts b/plugins/@eslint/eslint-plugin-forbidden-imports/src/index.ts new file mode 100644 index 0000000..ab95cc6 --- /dev/null +++ b/plugins/@eslint/eslint-plugin-forbidden-imports/src/index.ts @@ -0,0 +1,17 @@ +import noInternalImports from "./rules/no-internal-imports"; +import layerBoundaries from "./rules/layer-boundaries"; + +export const rules = { + "no-internal-imports": noInternalImports, + "layer-boundaries": layerBoundaries, +}; + +export const configs = { + recommended: { + plugins: ["@reflex"], + rules: { + "@reflex/no-internal-imports": "error", + "@reflex/layer-boundaries": "error", + }, + }, +}; diff --git a/plugins/@eslint/eslint-plugin-forbidden-imports/src/rules/layer-boundaries.ts b/plugins/@eslint/eslint-plugin-forbidden-imports/src/rules/layer-boundaries.ts new file mode 100644 index 0000000..a41fa6f --- /dev/null +++ b/plugins/@eslint/eslint-plugin-forbidden-imports/src/rules/layer-boundaries.ts @@ -0,0 +1,54 @@ +import type { Rule } from "eslint"; +import type { ImportDeclaration } from "estree"; + +const rule: Rule.RuleModule = { + meta: { + type: "problem", + docs: { + description: "Enforce Reflex layer boundaries", + recommended: true, + }, + messages: { + boundary: "Illegal import from '{{to}}' in layer '{{from}}'.", + }, + schema: [], + }, + + create(context) { + const filename = context.filename; + + const isApp = filename.includes("/apps/"); + const isPublicPkg = filename.includes("/packages/reflex"); + const isInternal = filename.includes("/packages/@reflex/"); + + return { + ImportDeclaration(node) { + const source = (node as ImportDeclaration).source.value; + if (typeof source !== "string") return; + + // apps can only import reflex / reflex-dom + if (isApp && source.startsWith("@reflex/")) { + context.report({ + node, + messageId: "boundary", + data: { from: "app", to: source }, + }); + } + + // public package can't import internal (except types maybe later) + if (isPublicPkg && source.startsWith("@reflex/")) { + context.report({ + node, + messageId: "boundary", + data: { from: "reflex", to: source }, + }); + } + + // internal can do anything + if (isInternal) return; + }, + }; + }, +}; + +export default rule; diff --git a/plugins/@eslint/eslint-plugin-forbidden-imports/src/rules/no-internal-imports.ts b/plugins/@eslint/eslint-plugin-forbidden-imports/src/rules/no-internal-imports.ts new file mode 100644 index 0000000..698e58c --- /dev/null +++ b/plugins/@eslint/eslint-plugin-forbidden-imports/src/rules/no-internal-imports.ts @@ -0,0 +1,41 @@ +import type { Rule } from "eslint"; +import type { ImportDeclaration } from "estree"; + +const rule: Rule.RuleModule = { + meta: { + type: "problem", + docs: { + description: "Disallow direct imports from @reflex/*", + recommended: true, + }, + messages: { + forbidden: + "Do not import from {{name}}. Use 'reflex' or 'reflex-dom' instead.", + }, + schema: [], + }, + + create(context) { + return { + ImportDeclaration(node) { + const source = (node as ImportDeclaration).source.value; + + if (typeof source !== "string") return; + + // allow inside @reflex packages themselves + const filename = context.filename; + if (filename.includes("/packages/@reflex/")) return; + + if (source.startsWith("@reflex/")) { + context.report({ + node, + messageId: "forbidden", + data: { name: source }, + }); + } + }, + }; + }, +}; + +export default rule; diff --git a/third-party/rigidify/tsconfig.json b/plugins/@eslint/eslint-plugin-forbidden-imports/tsconfig.json similarity index 51% rename from third-party/rigidify/tsconfig.json rename to plugins/@eslint/eslint-plugin-forbidden-imports/tsconfig.json index 9571f4f..c2ff8e2 100644 --- a/third-party/rigidify/tsconfig.json +++ b/plugins/@eslint/eslint-plugin-forbidden-imports/tsconfig.json @@ -1,11 +1,12 @@ { "compilerOptions": { - "target": "esnext", - "module": "ESNext", - "declaration": true, - "declarationDir": "dist", "outDir": "dist", + "rootDir": "src", + "declaration": true, + "module": "ESNext", + "target": "ES2020", + "moduleResolution": "Node", + "esModuleInterop": true, "strict": true - }, - "include": ["src"] + } } diff --git a/plugins/Readme.md b/plugins/Readme.md new file mode 100644 index 0000000..e69de29 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..bc758cb --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4989 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + 0x: + specifier: ^6.0.0 + version: 6.0.0 + '@changesets/cli': + specifier: ^2.27.0 + version: 2.29.7(@types/node@24.10.1) + '@eslint/js': + specifier: ^9.0.0 + version: 9.39.1 + eslint: + specifier: ^9.0.0 + version: 9.39.1 + fast-check: + specifier: ^4.3.0 + version: 4.3.0 + husky: + specifier: ^9.0.0 + version: 9.1.7 + lint-staged: + specifier: ^15.0.0 + version: 15.5.2 + prettier: + specifier: ^3.3.0 + version: 3.6.2 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@24.10.1)(typescript@5.9.3) + typescript: + specifier: ^5.6.0 + version: 5.9.3 + typescript-eslint: + specifier: ^8.0.0 + version: 8.46.4(eslint@9.39.1)(typescript@5.9.3) + vite: + specifier: ^6.0.0 + version: 6.4.1(@types/node@24.10.1)(yaml@2.8.1) + vitest: + specifier: ^4.0.0 + version: 4.0.9(@types/node@24.10.1)(yaml@2.8.1) + + packages/@reflex/contract: {} + + packages/@reflex/core: + devDependencies: + '@reflex/contract': + specifier: workspace:* + version: link:../contract + '@types/node': + specifier: ^24.10.1 + version: 24.10.1 + + packages/@reflex/runtime: + dependencies: + '@reflex/contract': + specifier: workspace:* + version: link:../contract + '@reflex/core': + specifier: workspace:* + version: link:../core + + packages/reflex: + dependencies: + '@reflex/core': + specifier: workspace:* + version: link:../@reflex/core + '@reflex/runtime': + specifier: workspace:* + version: link:../@reflex/runtime + + packages/reflex-dom: + dependencies: + reflex: + specifier: workspace:* + version: link:../reflex + +packages: + + 0x@6.0.0: + resolution: {integrity: sha512-4JrGHSPTaoEL3MZiKYH5BlNv67X2F48FmR3dKJZOoR/Z1CLVtVOwUb/n4PMf7B+sP9RPz+X50EbIFuwtfYguRQ==} + engines: {node: '>=8.5.0'} + hasBin: true + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@7.0.13': + resolution: {integrity: sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg==} + + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/cli@2.29.7': + resolution: {integrity: sha512-R7RqWoaksyyKXbKXBTbT4REdy22yH81mcFK6sWtqSanxUCbUi9Uf+6aqxZtDQouIqPdem2W56CdxXgsxdq7FLQ==} + hasBin: true + + '@changesets/config@3.1.1': + resolution: {integrity: sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + + '@changesets/get-release-plan@4.0.13': + resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.1': + resolution: {integrity: sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.5': + resolution: {integrity: sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + '@typescript-eslint/eslint-plugin@8.46.4': + resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.46.4 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.46.4': + resolution: {integrity: sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.46.4': + resolution: {integrity: sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.46.4': + resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.46.4': + resolution: {integrity: sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.46.4': + resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.46.4': + resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.46.4': + resolution: {integrity: sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.46.4': + resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.46.4': + resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/expect@4.0.9': + resolution: {integrity: sha512-C2vyXf5/Jfj1vl4DQYxjib3jzyuswMi/KHHVN2z+H4v16hdJ7jMZ0OGe3uOVIt6LyJsAofDdaJNIFEpQcrSTFw==} + + '@vitest/mocker@4.0.9': + resolution: {integrity: sha512-PUyaowQFHW+9FKb4dsvvBM4o025rWMlEDXdWRxIOilGaHREYTi5Q2Rt9VCgXgPy/hHZu1LeuXtrA/GdzOatP2g==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.9': + resolution: {integrity: sha512-Hor0IBTwEi/uZqB7pvGepyElaM8J75pYjrrqbC8ZYMB9/4n5QA63KC15xhT+sqHpdGWfdnPo96E8lQUxs2YzSQ==} + + '@vitest/runner@4.0.9': + resolution: {integrity: sha512-aF77tsXdEvIJRkj9uJZnHtovsVIx22Ambft9HudC+XuG/on1NY/bf5dlDti1N35eJT+QZLb4RF/5dTIG18s98w==} + + '@vitest/snapshot@4.0.9': + resolution: {integrity: sha512-r1qR4oYstPbnOjg0Vgd3E8ADJbi4ditCzqr+Z9foUrRhIy778BleNyZMeAJ2EjV+r4ASAaDsdciC9ryMy8xMMg==} + + '@vitest/spy@4.0.9': + resolution: {integrity: sha512-J9Ttsq0hDXmxmT8CUOWUr1cqqAj2FJRGTdyEjSR+NjoOGKEqkEWj+09yC0HhI8t1W6t4Ctqawl1onHgipJve1A==} + + '@vitest/utils@4.0.9': + resolution: {integrity: sha512-cEol6ygTzY4rUPvNZM19sDf7zGa35IYTm9wfzkHoT/f5jX10IOY7QleWSOh5T0e3I3WVozwK5Asom79qW8DiuQ==} + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-node@1.8.2: + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} + + acorn-walk@7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@7.2.0: + resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + engines: {node: '>=18'} + + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + asn1.js@4.10.1: + resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} + + assert@1.5.1: + resolution: {integrity: sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + bn.js@4.12.2: + resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} + + bn.js@5.2.2: + resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + browser-pack@6.1.0: + resolution: {integrity: sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==} + hasBin: true + + browser-process-hrtime@0.1.3: + resolution: {integrity: sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==} + + browser-resolve@2.0.0: + resolution: {integrity: sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==} + + browserify-aes@1.2.0: + resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} + + browserify-cipher@1.0.1: + resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==} + + browserify-des@1.0.2: + resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} + + browserify-rsa@4.1.1: + resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==} + engines: {node: '>= 0.10'} + + browserify-sign@4.2.5: + resolution: {integrity: sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==} + engines: {node: '>= 0.10'} + + browserify-zlib@0.2.0: + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + + browserify@17.0.1: + resolution: {integrity: sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==} + engines: {node: '>= 0.8'} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer-xor@1.0.3: + resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} + + buffer@5.2.1: + resolution: {integrity: sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==} + + builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + + cached-path-relative@1.1.0: + resolution: {integrity: sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camel-case@3.0.0: + resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} + + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} + engines: {node: '>=18'} + + chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cipher-base@1.0.7: + resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} + engines: {node: '>= 0.10'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + code-point-at@1.1.0: + resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combine-source-map@0.8.0: + resolution: {integrity: sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + console-browserify@1.2.0: + resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} + + constants-browserify@1.0.0: + resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} + + convert-source-map@1.1.3: + resolution: {integrity: sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + create-ecdh@4.0.4: + resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} + + create-hash@1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} + + create-hmac@1.1.7: + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypto-browserify@3.12.1: + resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} + engines: {node: '>= 0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-color@1.4.1: + resolution: {integrity: sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==} + + d3-color@2.0.0: + resolution: {integrity: sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==} + + d3-dispatch@1.0.6: + resolution: {integrity: sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==} + + d3-drag@1.2.5: + resolution: {integrity: sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==} + + d3-ease@1.0.7: + resolution: {integrity: sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==} + + d3-fg@6.14.0: + resolution: {integrity: sha512-M4QpFZOEvAq4ZDzwabJp2inL+KXS85T2SQl00zWwjnolaCJR+gHxUbT7Ha4GxTeW1NXwzbykhv/38I1fxQqbyg==} + + d3-format@2.0.0: + resolution: {integrity: sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==} + + d3-hierarchy@1.1.9: + resolution: {integrity: sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==} + + d3-interpolate@1.4.0: + resolution: {integrity: sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==} + + d3-interpolate@2.0.1: + resolution: {integrity: sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==} + + d3-scale@3.3.0: + resolution: {integrity: sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==} + + d3-selection@1.4.2: + resolution: {integrity: sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==} + + d3-time-format@3.0.0: + resolution: {integrity: sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==} + + d3-time@2.1.1: + resolution: {integrity: sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==} + + d3-timer@1.0.10: + resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==} + + d3-transition@1.3.2: + resolution: {integrity: sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==} + + d3-zoom@1.8.3: + resolution: {integrity: sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==} + + dash-ast@1.0.0: + resolution: {integrity: sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + defined@1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + + deps-sort@2.0.1: + resolution: {integrity: sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==} + hasBin: true + + des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + detective@5.2.1: + resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} + engines: {node: '>=0.8.0'} + hasBin: true + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + diffie-hellman@5.0.3: + resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + domain-browser@1.2.0: + resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==} + engines: {node: '>=0.4', npm: '>=1.2'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + env-string@1.0.1: + resolution: {integrity: sha512-/DhCJDf5DSFK32joQiWRpWrT0h7p3hVQfMKxiBb7Nt8C8IF8BYyPtclDnuGGLOoj16d/8udKeiE7JbkotDmorQ==} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-is-member-expression@1.0.0: + resolution: {integrity: sha512-Ec+X44CapIGExvSZN+pGkmr5p7HwUVQoPQSd458Lqwvaf4/61k/invHSh4BYK8OXnCkfEhWuIoG5hayKLQStIg==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + evp_bytestokey@1.0.3: + resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + execspawn@1.0.1: + resolution: {integrity: sha512-s2k06Jy9i8CUkYe0+DxRlvtkZoOkwwfhB+Xxo5HGUtrISVW2m98jO2tr67DGRFxZwkjQqloA3v/tNtjhBRBieg==} + + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + fast-check@4.3.0: + resolution: {integrity: sha512-JVw/DJSxVKl8uhCb7GrwanT9VWsCIdBkK3WpP37B/Au4pyaspriSjtrY2ApbSFwTg3ViPfniT13n75PhzE7VEQ==} + engines: {node: '>=12.17.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-assigned-identifiers@1.2.0: + resolution: {integrity: sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hash-base@3.0.5: + resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==} + engines: {node: '>= 0.10'} + + hash-base@3.1.2: + resolution: {integrity: sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==} + engines: {node: '>= 0.8'} + + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + + hsl-to-rgb-for-reals@1.1.1: + resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==} + + htmlescape@1.1.1: + resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==} + engines: {node: '>=0.10'} + + https-browserify@1.0.0: + resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} + + human-id@4.1.2: + resolution: {integrity: sha512-v/J+4Z/1eIJovEBdlV5TYj1IR+ZiohcYGRY+qN/oC9dAfKzVT023N/Bgw37hrKCoVRBvk3bqyzpr2PP5YeTMSg==} + hasBin: true + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + hyperscript-attribute-to-property@1.0.2: + resolution: {integrity: sha512-oerMul16jZCmrbNsUw8QgrtDzF8lKgFri1bKQjReLw1IhiiNkI59CWuzZjJDGT79UQ1YiWqXhJMv/tRMVqgtkA==} + + hyperx@2.5.4: + resolution: {integrity: sha512-iOkSh7Yse7lsN/B9y7OsevLWjeXPqGuHQ5SbwaiJM5xAhWFqhoN6erpK1dQsS12OFU36lyai1pnx1mmzWLQqcA==} + + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inline-source-map@0.6.3: + resolution: {integrity: sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==} + + insert-module-globals@7.2.1: + resolution: {integrity: sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==} + hasBin: true + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-boolean-attribute@0.0.1: + resolution: {integrity: sha512-0kXT52Scokg2Miscvsn5UVqg6y1691vcLJcagie1YHJB4zOEuAhMERLX992jtvaStGy2xQTqOtJhvmG/MK1T5w==} + + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@1.0.0: + resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + is-wsl@1.1.0: + resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} + engines: {node: '>=4'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + jsonstream2@3.0.0: + resolution: {integrity: sha512-8ngq2XB8NjYrpe3+Xtl9lFJl6RoV2dNT4I7iyaHwxUpTBwsj0AlAR7epGfeYVP0z4Z7KxMoSxRgJWrd2jmBT/Q==} + engines: {node: '>=5.10.0'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + labeled-stream-splicer@2.0.2: + resolution: {integrity: sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lint-staged@15.5.2: + resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==} + engines: {node: '>=18.12.0'} + hasBin: true + + listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.memoize@3.0.4: + resolution: {integrity: sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + lower-case@1.1.4: + resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} + + magic-string@0.23.2: + resolution: {integrity: sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + md5.js@1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + + merge-source-map@1.0.4: + resolution: {integrity: sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + miller-rabin@4.0.1: + resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} + hasBin: true + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + module-deps@6.2.3: + resolution: {integrity: sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==} + engines: {node: '>= 0.8.0'} + hasBin: true + + morphdom@2.7.7: + resolution: {integrity: sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mutexify@1.4.0: + resolution: {integrity: sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==} + + nanoassert@1.1.0: + resolution: {integrity: sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ==} + + nanobench@2.1.1: + resolution: {integrity: sha512-z+Vv7zElcjN+OpzAxAquUayFLGK3JI/ubCl0Oh64YQqsTGG09CGqieJVQw4ui8huDnnAgrvTv93qi5UaOoNj8A==} + hasBin: true + + nanohtml@1.10.0: + resolution: {integrity: sha512-r/3AQl+jxAxUIJRiKExUjBtFcE1cm4yTOsTIdVqqlxPNtBxJh522ANrcQYzdNHhPzbPgb7j6qujq6eGehBX0kg==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + no-case@2.3.2: + resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} + + normalize-html-whitespace@0.2.0: + resolution: {integrity: sha512-5CZAEQ4bQi8Msqw0GAT6rrkrjNN4ZKqAG3+jJMwms4O6XoMvh6ekwOueG4mRS1LbPUR1r9EdnhxxfpzMTOdzKw==} + engines: {node: '>= 0.10'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + number-is-nan@1.0.1: + resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + on-net-listen@1.1.2: + resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==} + engines: {node: '>=9.4.0 || ^8.9.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + opn@5.5.0: + resolution: {integrity: sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==} + engines: {node: '>=4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-browserify@0.3.0: + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parents@1.0.1: + resolution: {integrity: sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==} + + parse-asn1@5.1.9: + resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==} + engines: {node: '>= 0.10'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-platform@0.11.15: + resolution: {integrity: sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==} + engines: {node: '>= 0.8.0'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pbkdf2@3.1.5: + resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==} + engines: {node: '>= 0.10'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + pretty-hrtime@1.0.3: + resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} + engines: {node: '>= 0.8'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + public-encrypt@4.0.3: + resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + pumpify@2.0.1: + resolution: {integrity: sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==} + + punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + querystring-es3@0.2.1: + resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} + engines: {node: '>=0.4.x'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + randomfill@1.0.4: + resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} + + read-only-stream@2.0.0: + resolution: {integrity: sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + ripemd160@2.0.3: + resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} + engines: {node: '>= 0.8'} + + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + + shasum-object@1.0.1: + resolution: {integrity: sha512-SsC+1tW7XKQ/94D4k1JhLmjDFpVGET/Nf54jVDtbavbALf8Zhp0Td9zTlxScjMW6nbEIrpADtPWfLk9iCXzHDQ==} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + single-line-log@1.1.2: + resolution: {integrity: sha512-awzaaIPtYFdexLr6TBpcZSGPB6D1RInNO/qNetgaJloPDF/D0GkVtLvGEp8InfmLV7CyLyQ5fIRP+tVN/JmWQA==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + + stream-combiner2@1.1.1: + resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} + + stream-http@3.2.0: + resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + stream-splicer@2.0.1: + resolution: {integrity: sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@1.0.2: + resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} + engines: {node: '>=0.10.0'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + subarg@1.0.0: + resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==} + + supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + syntax-error@1.4.0: + resolution: {integrity: sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==} + + tachyons@4.12.0: + resolution: {integrity: sha512-2nA2IrYFy3raCM9fxJ2KODRGHVSZNTW3BR0YnlGsLUf1DA3pk3YfWZ/DdfbnZK6zLZS+jUenlUGJsKcA5fUiZg==} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + through2@3.0.2: + resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} + + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + timers-browserify@1.4.2: + resolution: {integrity: sha512-PIxwAupJZiYU4JmVZYwXp9FKsHMXb5h0ZEFyuXTAn8WLHOlcij+FEcbrvDsom1o5dr1YggEtFbECvGCW2sT53Q==} + engines: {node: '>=0.6.0'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + transform-ast@2.4.4: + resolution: {integrity: sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tty-browserify@0.0.1: + resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-component@0.0.1: + resolution: {integrity: sha512-mDZRBQS2yZkwRQKfjJvQ8UIYJeBNNWCq+HBNstl9N5s9jZ4dkVYXEGkVPsSCEh5Ld4JM1kmrZTzjnrqSAIQ7dw==} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript-eslint@8.46.4: + resolution: {integrity: sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + umd@3.0.3: + resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==} + hasBin: true + + undeclared-identifiers@1.1.3: + resolution: {integrity: sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + upper-case@1.1.3: + resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url@0.11.4: + resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} + engines: {node: '>= 0.4'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util-extend@1.0.3: + resolution: {integrity: sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==} + + util@0.10.4: + resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.9: + resolution: {integrity: sha512-E0Ja2AX4th+CG33yAFRC+d1wFx2pzU5r6HtG6LiPSE04flaE0qB6YyjSw9ZcpJAtVPfsvZGtJlKWZpuW7EHRxg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.9 + '@vitest/browser-preview': 4.0.9 + '@vitest/browser-webdriverio': 4.0.9 + '@vitest/ui': 4.0.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vm-browserify@1.1.2: + resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + 0x@6.0.0: + dependencies: + ajv: 8.17.1 + browserify: 17.0.1 + concat-stream: 2.0.0 + d3-fg: 6.14.0 + debounce: 1.2.1 + debug: 4.4.3 + end-of-stream: 1.4.5 + env-string: 1.0.1 + escape-string-regexp: 4.0.0 + execspawn: 1.0.1 + fs-extra: 10.1.0 + has-unicode: 2.0.1 + hsl-to-rgb-for-reals: 1.1.1 + jsonstream2: 3.0.0 + make-dir: 3.1.0 + minimist: 1.2.8 + morphdom: 2.7.7 + nanohtml: 1.10.0 + on-net-listen: 1.1.2 + opn: 5.5.0 + pump: 3.0.3 + pumpify: 2.0.1 + semver: 7.7.3 + single-line-log: 1.1.2 + split2: 4.2.0 + tachyons: 4.12.0 + through2: 4.0.2 + which: 2.0.2 + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.4': {} + + '@changesets/apply-release-plan@7.0.13': + dependencies: + '@changesets/config': 3.1.1 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.3 + + '@changesets/assemble-release-plan@6.0.9': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.3 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.29.7(@types/node@24.10.1)': + dependencies: + '@changesets/apply-release-plan': 7.0.13 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.1 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.13 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@24.10.1) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.7.3 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.1': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.3': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.7.3 + + '@changesets/get-release-plan@4.0.13': + dependencies: + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.5 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.1': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 3.14.1 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.5': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.1 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.1.2 + prettier: 2.8.8 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inquirer/external-editor@1.0.3(@types/node@24.10.1)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.0 + optionalDependencies: + '@types/node': 24.10.1 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.28.4 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.28.4 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@rollup/rollup-android-arm-eabi@4.52.5': + optional: true + + '@rollup/rollup-android-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-x64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.5': + optional: true + + '@standard-schema/spec@1.0.0': {} + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@12.20.55': {} + + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + '@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.4 + '@typescript-eslint/type-utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.4 + eslint: 9.39.1 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.46.4 + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.4 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.46.4(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) + '@typescript-eslint/types': 8.46.4 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.46.4': + dependencies: + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/visitor-keys': 8.46.4 + + '@typescript-eslint/tsconfig-utils@8.46.4(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.46.4': {} + + '@typescript-eslint/typescript-estree@8.46.4(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.46.4(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/visitor-keys': 8.46.4 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.46.4 + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.46.4': + dependencies: + '@typescript-eslint/types': 8.46.4 + eslint-visitor-keys: 4.2.1 + + '@vitest/expect@4.0.9': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.9 + '@vitest/utils': 4.0.9 + chai: 6.2.1 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.9(vite@6.4.1(@types/node@24.10.1)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 4.0.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.1(@types/node@24.10.1)(yaml@2.8.1) + + '@vitest/pretty-format@4.0.9': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.9': + dependencies: + '@vitest/utils': 4.0.9 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.9': + dependencies: + '@vitest/pretty-format': 4.0.9 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.9': {} + + '@vitest/utils@4.0.9': + dependencies: + '@vitest/pretty-format': 4.0.9 + tinyrainbow: 3.0.3 + + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-node@1.8.2: + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + xtend: 4.0.2 + + acorn-walk@7.2.0: {} + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@7.4.1: {} + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-colors@4.1.3: {} + + ansi-escapes@7.2.0: + dependencies: + environment: 1.1.0 + + ansi-regex@2.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@2.2.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + asn1.js@4.10.1: + dependencies: + bn.js: 4.12.2 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + assert@1.5.1: + dependencies: + object.assign: 4.1.7 + util: 0.10.4 + + assertion-error@2.0.1: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + bn.js@4.12.2: {} + + bn.js@5.2.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brorand@1.1.0: {} + + browser-pack@6.1.0: + dependencies: + JSONStream: 1.3.5 + combine-source-map: 0.8.0 + defined: 1.0.1 + safe-buffer: 5.2.1 + through2: 2.0.5 + umd: 3.0.3 + + browser-process-hrtime@0.1.3: {} + + browser-resolve@2.0.0: + dependencies: + resolve: 1.22.11 + + browserify-aes@1.2.0: + dependencies: + buffer-xor: 1.0.3 + cipher-base: 1.0.7 + create-hash: 1.2.0 + evp_bytestokey: 1.0.3 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + browserify-cipher@1.0.1: + dependencies: + browserify-aes: 1.2.0 + browserify-des: 1.0.2 + evp_bytestokey: 1.0.3 + + browserify-des@1.0.2: + dependencies: + cipher-base: 1.0.7 + des.js: 1.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + browserify-rsa@4.1.1: + dependencies: + bn.js: 5.2.2 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + + browserify-sign@4.2.5: + dependencies: + bn.js: 5.2.2 + browserify-rsa: 4.1.1 + create-hash: 1.2.0 + create-hmac: 1.1.7 + elliptic: 6.6.1 + inherits: 2.0.4 + parse-asn1: 5.1.9 + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + + browserify-zlib@0.2.0: + dependencies: + pako: 1.0.11 + + browserify@17.0.1: + dependencies: + JSONStream: 1.3.5 + assert: 1.5.1 + browser-pack: 6.1.0 + browser-resolve: 2.0.0 + browserify-zlib: 0.2.0 + buffer: 5.2.1 + cached-path-relative: 1.1.0 + concat-stream: 1.6.2 + console-browserify: 1.2.0 + constants-browserify: 1.0.0 + crypto-browserify: 3.12.1 + defined: 1.0.1 + deps-sort: 2.0.1 + domain-browser: 1.2.0 + duplexer2: 0.1.4 + events: 3.3.0 + glob: 7.2.3 + hasown: 2.0.2 + htmlescape: 1.1.1 + https-browserify: 1.0.0 + inherits: 2.0.4 + insert-module-globals: 7.2.1 + labeled-stream-splicer: 2.0.2 + mkdirp-classic: 0.5.3 + module-deps: 6.2.3 + os-browserify: 0.3.0 + parents: 1.0.1 + path-browserify: 1.0.1 + process: 0.11.10 + punycode: 1.4.1 + querystring-es3: 0.2.1 + read-only-stream: 2.0.0 + readable-stream: 2.3.8 + resolve: 1.22.11 + shasum-object: 1.0.1 + shell-quote: 1.8.3 + stream-browserify: 3.0.0 + stream-http: 3.2.0 + string_decoder: 1.3.0 + subarg: 1.0.0 + syntax-error: 1.4.0 + through2: 2.0.5 + timers-browserify: 1.4.2 + tty-browserify: 0.0.1 + url: 0.11.4 + util: 0.12.5 + vm-browserify: 1.1.2 + xtend: 4.0.2 + + buffer-from@1.1.2: {} + + buffer-xor@1.0.3: {} + + buffer@5.2.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builtin-status-codes@3.0.0: {} + + cached-path-relative@1.1.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camel-case@3.0.0: + dependencies: + no-case: 2.3.2 + upper-case: 1.1.3 + + chai@6.2.1: {} + + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chardet@2.1.1: {} + + ci-info@3.9.0: {} + + cipher-base@1.0.7: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + code-point-at@1.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + combine-source-map@0.8.0: + dependencies: + convert-source-map: 1.1.3 + inline-source-map: 0.6.3 + lodash.memoize: 3.0.4 + source-map: 0.5.7 + + commander@13.1.0: {} + + concat-map@0.0.1: {} + + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + console-browserify@1.2.0: {} + + constants-browserify@1.0.0: {} + + convert-source-map@1.1.3: {} + + convert-source-map@1.9.0: {} + + core-util-is@1.0.3: {} + + create-ecdh@4.0.4: + dependencies: + bn.js: 4.12.2 + elliptic: 6.6.1 + + create-hash@1.2.0: + dependencies: + cipher-base: 1.0.7 + inherits: 2.0.4 + md5.js: 1.3.5 + ripemd160: 2.0.3 + sha.js: 2.4.12 + + create-hmac@1.1.7: + dependencies: + cipher-base: 1.0.7 + create-hash: 1.2.0 + inherits: 2.0.4 + ripemd160: 2.0.3 + safe-buffer: 5.2.1 + sha.js: 2.4.12 + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-browserify@3.12.1: + dependencies: + browserify-cipher: 1.0.1 + browserify-sign: 4.2.5 + create-ecdh: 4.0.4 + create-hash: 1.2.0 + create-hmac: 1.1.7 + diffie-hellman: 5.0.3 + hash-base: 3.0.5 + inherits: 2.0.4 + pbkdf2: 3.1.5 + public-encrypt: 4.0.3 + randombytes: 2.1.0 + randomfill: 1.0.4 + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-color@1.4.1: {} + + d3-color@2.0.0: {} + + d3-dispatch@1.0.6: {} + + d3-drag@1.2.5: + dependencies: + d3-dispatch: 1.0.6 + d3-selection: 1.4.2 + + d3-ease@1.0.7: {} + + d3-fg@6.14.0: + dependencies: + d3-array: 2.12.1 + d3-dispatch: 1.0.6 + d3-ease: 1.0.7 + d3-hierarchy: 1.1.9 + d3-scale: 3.3.0 + d3-selection: 1.4.2 + d3-zoom: 1.8.3 + escape-string-regexp: 1.0.5 + hsl-to-rgb-for-reals: 1.1.1 + + d3-format@2.0.0: {} + + d3-hierarchy@1.1.9: {} + + d3-interpolate@1.4.0: + dependencies: + d3-color: 1.4.1 + + d3-interpolate@2.0.1: + dependencies: + d3-color: 2.0.0 + + d3-scale@3.3.0: + dependencies: + d3-array: 2.12.1 + d3-format: 2.0.0 + d3-interpolate: 2.0.1 + d3-time: 2.1.1 + d3-time-format: 3.0.0 + + d3-selection@1.4.2: {} + + d3-time-format@3.0.0: + dependencies: + d3-time: 2.1.1 + + d3-time@2.1.1: + dependencies: + d3-array: 2.12.1 + + d3-timer@1.0.10: {} + + d3-transition@1.3.2: + dependencies: + d3-color: 1.4.1 + d3-dispatch: 1.0.6 + d3-ease: 1.0.7 + d3-interpolate: 1.4.0 + d3-selection: 1.4.2 + d3-timer: 1.0.10 + + d3-zoom@1.8.3: + dependencies: + d3-dispatch: 1.0.6 + d3-drag: 1.2.5 + d3-interpolate: 1.4.0 + d3-selection: 1.4.2 + d3-transition: 1.3.2 + + dash-ast@1.0.0: {} + + debounce@1.2.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + defined@1.0.1: {} + + deps-sort@2.0.1: + dependencies: + JSONStream: 1.3.5 + shasum-object: 1.0.1 + subarg: 1.0.0 + through2: 2.0.5 + + des.js@1.1.0: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + detect-indent@6.1.0: {} + + detective@5.2.1: + dependencies: + acorn-node: 1.8.2 + defined: 1.0.1 + minimist: 1.2.8 + + diff@4.0.2: {} + + diffie-hellman@5.0.3: + dependencies: + bn.js: 4.12.2 + miller-rabin: 4.0.1 + randombytes: 2.1.0 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + domain-browser@1.2.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + + duplexify@4.1.3: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 + + elliptic@6.6.1: + dependencies: + bn.js: 4.12.2 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + emoji-regex@10.6.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + env-string@1.0.1: {} + + environment@1.1.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-is-member-expression@1.0.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + eventemitter3@5.0.1: {} + + events@3.3.0: {} + + evp_bytestokey@1.0.3: + dependencies: + md5.js: 1.3.5 + safe-buffer: 5.2.1 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + execspawn@1.0.1: + dependencies: + util-extend: 1.0.3 + + expect-type@1.2.2: {} + + extendable-error@0.1.7: {} + + fast-check@4.3.0: + dependencies: + pure-rand: 7.0.1 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.0: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + generator-function@2.0.1: {} + + get-assigned-identifiers@1.2.0: {} + + get-east-asian-width@1.4.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@8.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: {} + + hash-base@3.0.5: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + + hash-base@3.1.2: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + hsl-to-rgb-for-reals@1.1.1: {} + + htmlescape@1.1.1: {} + + https-browserify@1.0.0: {} + + human-id@4.1.2: {} + + human-signals@5.0.0: {} + + husky@9.1.7: {} + + hyperscript-attribute-to-property@1.0.2: {} + + hyperx@2.5.4: + dependencies: + hyperscript-attribute-to-property: 1.0.2 + + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + inline-source-map@0.6.3: + dependencies: + source-map: 0.5.7 + + insert-module-globals@7.2.1: + dependencies: + JSONStream: 1.3.5 + acorn-node: 1.8.2 + combine-source-map: 0.8.0 + concat-stream: 1.6.2 + is-buffer: 1.1.6 + path-is-absolute: 1.0.1 + process: 0.11.10 + through2: 2.0.5 + undeclared-identifiers: 1.1.3 + xtend: 4.0.2 + + internmap@1.0.1: {} + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-boolean-attribute@0.0.1: {} + + is-buffer@1.1.6: {} + + is-buffer@2.0.5: {} + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@1.0.0: + dependencies: + number-is-nan: 1.0.1 + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-stream@3.0.0: {} + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-windows@1.0.2: {} + + is-wsl@1.1.0: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonparse@1.3.1: {} + + jsonstream2@3.0.0: + dependencies: + jsonparse: 1.3.1 + through2: 3.0.2 + type-component: 0.0.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + labeled-stream-splicer@2.0.2: + dependencies: + inherits: 2.0.4 + stream-splicer: 2.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lint-staged@15.5.2: + dependencies: + chalk: 5.6.2 + commander: 13.1.0 + debug: 4.4.3 + execa: 8.0.1 + lilconfig: 3.1.3 + listr2: 8.3.3 + micromatch: 4.0.8 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.1 + transitivePeerDependencies: + - supports-color + + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.memoize@3.0.4: {} + + lodash.merge@4.6.2: {} + + lodash.startcase@4.4.0: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.2.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + lower-case@1.1.4: {} + + magic-string@0.23.2: + dependencies: + sourcemap-codec: 1.4.8 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + make-error@1.3.6: {} + + math-intrinsics@1.1.0: {} + + md5.js@1.3.5: + dependencies: + hash-base: 3.0.5 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + merge-source-map@1.0.4: + dependencies: + source-map: 0.5.7 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + miller-rabin@4.0.1: + dependencies: + bn.js: 4.12.2 + brorand: 1.1.0 + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + module-deps@6.2.3: + dependencies: + JSONStream: 1.3.5 + browser-resolve: 2.0.0 + cached-path-relative: 1.1.0 + concat-stream: 1.6.2 + defined: 1.0.1 + detective: 5.2.1 + duplexer2: 0.1.4 + inherits: 2.0.4 + parents: 1.0.1 + readable-stream: 2.3.8 + resolve: 1.22.11 + stream-combiner2: 1.1.1 + subarg: 1.0.0 + through2: 2.0.5 + xtend: 4.0.2 + + morphdom@2.7.7: {} + + mri@1.2.0: {} + + ms@2.1.3: {} + + mutexify@1.4.0: + dependencies: + queue-tick: 1.0.1 + + nanoassert@1.1.0: {} + + nanobench@2.1.1: + dependencies: + browser-process-hrtime: 0.1.3 + chalk: 1.1.3 + mutexify: 1.4.0 + pretty-hrtime: 1.0.3 + + nanohtml@1.10.0: + dependencies: + acorn-node: 1.8.2 + camel-case: 3.0.0 + convert-source-map: 1.9.0 + estree-is-member-expression: 1.0.0 + hyperx: 2.5.4 + is-boolean-attribute: 0.0.1 + nanoassert: 1.1.0 + nanobench: 2.1.1 + normalize-html-whitespace: 0.2.0 + through2: 2.0.5 + transform-ast: 2.4.4 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + no-case@2.3.2: + dependencies: + lower-case: 1.1.4 + + normalize-html-whitespace@0.2.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + number-is-nan@1.0.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + on-net-listen@1.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + opn@5.5.0: + dependencies: + is-wsl: 1.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-browserify@0.3.0: {} + + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + + pako@1.0.11: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parents@1.0.1: + dependencies: + path-platform: 0.11.15 + + parse-asn1@5.1.9: + dependencies: + asn1.js: 4.10.1 + browserify-aes: 1.2.0 + evp_bytestokey: 1.0.3 + pbkdf2: 3.1.5 + safe-buffer: 5.2.1 + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-platform@0.11.15: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + pbkdf2@3.1.5: + dependencies: + create-hash: 1.2.0 + create-hmac: 1.1.7 + ripemd160: 2.0.3 + safe-buffer: 5.2.1 + sha.js: 2.4.12 + to-buffer: 1.2.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pify@4.0.1: {} + + possible-typed-array-names@1.1.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@2.8.8: {} + + prettier@3.6.2: {} + + pretty-hrtime@1.0.3: {} + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + public-encrypt@4.0.3: + dependencies: + bn.js: 4.12.2 + browserify-rsa: 4.1.1 + create-hash: 1.2.0 + parse-asn1: 5.1.9 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + pumpify@2.0.1: + dependencies: + duplexify: 4.1.3 + inherits: 2.0.4 + pump: 3.0.3 + + punycode@1.4.1: {} + + punycode@2.3.1: {} + + pure-rand@7.0.1: {} + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + quansync@0.2.11: {} + + querystring-es3@0.2.1: {} + + queue-microtask@1.2.3: {} + + queue-tick@1.0.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + randomfill@1.0.4: + dependencies: + randombytes: 2.1.0 + safe-buffer: 5.2.1 + + read-only-stream@2.0.0: + dependencies: + readable-stream: 2.3.8 + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + ripemd160@2.0.3: + dependencies: + hash-base: 3.1.2 + inherits: 2.0.4 + + rollup@4.52.5: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + shasum-object@1.0.1: + dependencies: + fast-safe-stringify: 2.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + single-line-log@1.1.2: + dependencies: + string-width: 1.0.2 + + slash@3.0.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + source-map-js@1.2.1: {} + + source-map@0.5.7: {} + + sourcemap-codec@1.4.8: {} + + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + stream-combiner2@1.1.1: + dependencies: + duplexer2: 0.1.4 + readable-stream: 2.3.8 + + stream-http@3.2.0: + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + xtend: 4.0.2 + + stream-shift@1.0.3: {} + + stream-splicer@2.0.1: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + + string-argv@0.3.2: {} + + string-width@1.0.2: + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + subarg@1.0.0: + dependencies: + minimist: 1.2.8 + + supports-color@2.0.0: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + syntax-error@1.4.0: + dependencies: + acorn-node: 1.8.2 + + tachyons@4.12.0: {} + + term-size@2.2.1: {} + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + through2@3.0.2: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + + through@2.3.8: {} + + timers-browserify@1.4.2: + dependencies: + process: 0.11.10 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + transform-ast@2.4.4: + dependencies: + acorn-node: 1.8.2 + convert-source-map: 1.9.0 + dash-ast: 1.0.0 + is-buffer: 2.0.5 + magic-string: 0.23.2 + merge-source-map: 1.0.4 + nanobench: 2.1.1 + + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 24.10.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tty-browserify@0.0.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-component@0.0.1: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typedarray@0.0.6: {} + + typescript-eslint@8.46.4(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + umd@3.0.3: {} + + undeclared-identifiers@1.1.3: + dependencies: + acorn-node: 1.8.2 + dash-ast: 1.0.0 + get-assigned-identifiers: 1.2.0 + simple-concat: 1.0.1 + xtend: 4.0.2 + + undici-types@7.16.0: {} + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + upper-case@1.1.3: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url@0.11.4: + dependencies: + punycode: 1.4.1 + qs: 6.14.0 + + util-deprecate@1.0.2: {} + + util-extend@1.0.3: {} + + util@0.10.4: + dependencies: + inherits: 2.0.3 + + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.2 + is-typed-array: 1.1.15 + which-typed-array: 1.1.19 + + v8-compile-cache-lib@3.0.1: {} + + vite@6.4.1(@types/node@24.10.1)(yaml@2.8.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.5 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + yaml: 2.8.1 + + vitest@4.0.9(@types/node@24.10.1)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.9 + '@vitest/mocker': 4.0.9(vite@6.4.1(@types/node@24.10.1)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.9 + '@vitest/runner': 4.0.9 + '@vitest/snapshot': 4.0.9 + '@vitest/spy': 4.0.9 + '@vitest/utils': 4.0.9 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 6.4.1(@types/node@24.10.1)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.10.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vm-browserify@1.1.2: {} + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + xtend@4.0.2: {} + + yaml@2.8.1: {} + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..b200b1a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: +- 'packages/*' +- "packages/@reflex/*" diff --git a/theory/Readme.md b/theory/Readme.md new file mode 100644 index 0000000..7b01213 --- /dev/null +++ b/theory/Readme.md @@ -0,0 +1 @@ +There will be are formal prove \ No newline at end of file diff --git a/third-party/README.md b/third-party/README.md deleted file mode 100644 index 9f946bd..0000000 --- a/third-party/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Rigidify & My Passion Libraries - -This repository also contains several **third-party libraries** — all of which I developed as part of my personal passion projects. These modules are fully modular, seamlessly integrated for demonstration purposes, and highlight advanced data structures, innovative algorithms, and cutting-edge design patterns. - -These libraries emerged from experimental development and creative exploration, serving both as testbeds for novel ideas and as examples of how complex concepts can be implemented efficiently and elegantly. They reflect a commitment to craftsmanship and a drive to push the boundaries of conventional solutions. diff --git a/third-party/pattern-matching/.editorconfig b/third-party/pattern-matching/.editorconfig deleted file mode 100644 index 003897b..0000000 --- a/third-party/pattern-matching/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -end_of_line = lf -charset = utf-8 -insert_final_newline = true -trim_trailing_whitespace = true - -[{*.js,*.mjs,*.ts,*.json,*.yml}] -indent_size = 2 -indent_style = space \ No newline at end of file diff --git a/third-party/pattern-matching/.gitignore b/third-party/pattern-matching/.gitignore deleted file mode 100644 index 524f91a..0000000 --- a/third-party/pattern-matching/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -.DS_STORE -node_modules -.flowconfig -*~ -*.pyc -.grunt -_SpecRunner.html -__benchmarks__ -build/ -remote-repo/ -coverage/ -*.log* -*.sublime-project -*.sublime-workspace -.idea -*.iml -.vscode -*.swp -*.swo -drafts/ -package-lock.json -mails/ \ No newline at end of file diff --git a/third-party/pattern-matching/.npmignore b/third-party/pattern-matching/.npmignore deleted file mode 100644 index 84784ea..0000000 --- a/third-party/pattern-matching/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -*.log -.DS_Store -CHANGELOG.md \ No newline at end of file diff --git a/third-party/pattern-matching/.prettierignore b/third-party/pattern-matching/.prettierignore deleted file mode 100644 index 84784ea..0000000 --- a/third-party/pattern-matching/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -*.log -.DS_Store -CHANGELOG.md \ No newline at end of file diff --git a/third-party/pattern-matching/AUTHORS b/third-party/pattern-matching/AUTHORS deleted file mode 100644 index 5bb4500..0000000 --- a/third-party/pattern-matching/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Andrii Volynets \ No newline at end of file diff --git a/third-party/pattern-matching/LICENSE b/third-party/pattern-matching/LICENSE deleted file mode 100644 index 8a83559..0000000 --- a/third-party/pattern-matching/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Andrii Volynets - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/third-party/pattern-matching/reactor-dsl.d.ts b/third-party/pattern-matching/reactor-dsl.d.ts deleted file mode 100644 index bc8acc1..0000000 --- a/third-party/pattern-matching/reactor-dsl.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -// reactor-dsl.d.ts - -/** Путь в объекте — либо массив сегментов, либо строка с паттернами */ -export type Path = string | Array; - -/** Параметры, извлекаемые из паттерна */ -export type Params = Record; - -/** Callback при изменении значения */ -export type ObserverCallback = (value: T, params?: Params) => void; - -/** Интерфейс реактивного объекта */ -export interface Reactor { - /** Подписка на изменения по паттерну */ - observe(path: Path, callback: ObserverCallback): void; - - /** Отписка по паттерну */ - unobserve(path: Path, callback: ObserverCallback): void; - - /** Обновление значения по пути (вызов реакции) */ - update(path: Path, value: any): void; - - /** Получение значения по пути */ - get(path: Path): any; - - /** Проверка соответствия паттерну без подписки */ - match(path: Path): Params | null; -} - -/** Создание реактора поверх обычного объекта */ -export function createReactor(state: Record): Reactor; - - -// const state = { -// user: { name: "Alice" <- the name is reactive primitive here, age: 25 }, -// logs: [ -// { level: "info" <- the level is reactive primitive here, msg: "started" }, -// { level: "error", msg: "crash" } -// ] -// }; - -// reactor.observe("user.name", value => { -// console.log("Name changed:", value); -// }); - -// reactor.observe("logs.*.level", (level, idx) => { -// console.log("Log", idx, "level changed:", level); -// }); diff --git a/third-party/rigidify/.editorconfig b/third-party/rigidify/.editorconfig deleted file mode 100644 index 003897b..0000000 --- a/third-party/rigidify/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -end_of_line = lf -charset = utf-8 -insert_final_newline = true -trim_trailing_whitespace = true - -[{*.js,*.mjs,*.ts,*.json,*.yml}] -indent_size = 2 -indent_style = space \ No newline at end of file diff --git a/third-party/rigidify/.gitignore b/third-party/rigidify/.gitignore deleted file mode 100644 index 524f91a..0000000 --- a/third-party/rigidify/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -.DS_STORE -node_modules -.flowconfig -*~ -*.pyc -.grunt -_SpecRunner.html -__benchmarks__ -build/ -remote-repo/ -coverage/ -*.log* -*.sublime-project -*.sublime-workspace -.idea -*.iml -.vscode -*.swp -*.swo -drafts/ -package-lock.json -mails/ \ No newline at end of file diff --git a/third-party/rigidify/.npmignore b/third-party/rigidify/.npmignore deleted file mode 100644 index 84784ea..0000000 --- a/third-party/rigidify/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -*.log -.DS_Store -CHANGELOG.md \ No newline at end of file diff --git a/third-party/rigidify/.prettierignore b/third-party/rigidify/.prettierignore deleted file mode 100644 index 84784ea..0000000 --- a/third-party/rigidify/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -*.log -.DS_Store -CHANGELOG.md \ No newline at end of file diff --git a/third-party/rigidify/AUTHORS b/third-party/rigidify/AUTHORS deleted file mode 100644 index 5bb4500..0000000 --- a/third-party/rigidify/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Andrii Volynets \ No newline at end of file diff --git a/third-party/rigidify/LICENSE b/third-party/rigidify/LICENSE deleted file mode 100644 index 8a83559..0000000 --- a/third-party/rigidify/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Andrii Volynets - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/third-party/rigidify/README.md b/third-party/rigidify/README.md deleted file mode 100644 index 4618802..0000000 --- a/third-party/rigidify/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# Rigidify - -**Rigidify** is a high-performance, immutable state management library for JavaScript and TypeScript. -It provides a simple, predictable API for working with nested state trees, inspired by persistent data structures and V8-friendly optimizations. - -Think of it as *path-copy immutable state* without proxies, designed for both UI and larger data structures. - ---- - -## Features - -- Immutable state updates with **minimal copies**: only the path to the modified node is copied. -- **V8-optimized**: predictable object shapes, inline caching friendly, avoids `structuredClone`. -- **Batch updates**: mutate multiple paths in one operation efficiently. -- **Draft-like mutation API**: mutate nested paths naturally with callback syntax. -- **High-level API** that can easily swap the backend to a more advanced structure like **MSPG (Matrix-Sharded Persistent Grid)**. -- Fully TypeScript-typed for developer ergonomics. - ---- - -## Installation - -```bash -npm install rigidify -# or -yarn add rigidify -``` - ---- - -## Usage - -### Basic `set` / `get` - -```ts -import { create, get, set } from 'rigidify'; - -let state = create({ user: { profile: { name: 'Alice' } } }); - -console.log(get(state, ['user', 'profile', 'name'])); // Alice - -state = set(state, ['user', 'profile', 'name'], 'Bob'); - -console.log(get(state, ['user', 'profile', 'name'])); // Bob -``` - -### Batch updates - -```ts -import { batch } from 'rigidify'; - -state = batch(state, [ - { path: ['user', 'profile', 'age'], value: 30 }, - { path: ['settings', 'theme'], value: 'dark' }, -]); -``` - -### Draft-like mutation - -```ts -import { mutate } from 'rigidify'; - -state = mutate(state, ['user', 'profile'], draft => { - draft.active = true; - draft.name = 'Charlie'; -}); -``` - ---- - -## API - -* `create(initialState)` — initialize a snapshot -* `get(state, path)` — retrieve a value at a nested path -* `set(state, path, value)` — immutable update at a single path -* `batch(state, updates)` — immutable updates at multiple paths -* `mutate(state, path, callback)` — draft-like mutation for easier updates -* `toPlainObject(state)` — get a plain JS object copy for debugging - ---- - -## Why Rigidify? - -Rigidify is designed for: - -* Large, deeply nested state trees -* Predictable performance on V8 engines -* Codebases where immutability is required but Proxy-based solutions (like Immer) are too much -* Projects that may later adopt low-level memory-efficient backends (MSPG) - ---- - -## Contributing - -Contributions are welcome! Please open issues or pull requests on GitHub. - ---- - -## License - -MIT - diff --git a/third-party/rigidify/package.json b/third-party/rigidify/package.json deleted file mode 100644 index 8d423a2..0000000 --- a/third-party/rigidify/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "rigidify", - "version": "0.1.0", - "author": "Andrii Volynets ", - "license": "MIT", - "description": "Rigidify: high-performance immutable state management for JavaScript/TypeScript applications.", - "keywords": [ - "immutable", - "state-management", - "javascript", - "typescript", - "path-copy", - "v8-optimized", - "metarhia", - "util" - ], - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "types": "dist/index.d.ts", - "files": [ - "dist", - "README.md", - "LICENSE" - ], - "scripts": { - "build": "rollup -c", - "test": "node tests/run.js", - "lint": "eslint src --ext .ts", - "prepare": "npm run build" - }, - "repository": { - "type": "git", - "url": "https://github.com/your-username/rigidify.git" - }, - "bugs": { - "url": "https://github.com/your-username/rigidify/issues" - }, - "homepage": "https://github.com/your-username/rigidify#readme", - "devDependencies": { - "rollup": "^4.50.2", - "rollup-plugin-typescript2": "^0.36.0", - "typescript": "^5.9.2", - "eslint": "^8.50.0" - }, - "engines": { - "node": ">=18.0.0" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/third-party/rigidify/src/index.d.ts b/third-party/rigidify/src/index.d.ts deleted file mode 100644 index ce539d2..0000000 --- a/third-party/rigidify/src/index.d.ts +++ /dev/null @@ -1,99 +0,0 @@ -declare const __DEV__: boolean - -/** - * A path in the state tree. - * Each element can be a string (object key) or a number (array index). - */ -export type Path = Array; - -/** - * A single update operation: set a value at a given path. - */ -export interface Update { - path: Path; - value: unknown; -} - -/** - * Backend interface — abstraction for different implementations - * (naive object copying, matrix-sharded buffer, etc.). - */ -export interface Backend { - /** - * Get the value at a given path in the state. - */ - get(state: TState, path: Path): unknown; - - /** - * Set a value at a given path, producing a new immutable state. - */ - set(state: TState, path: Path, value: unknown): TState; - - /** - * Apply multiple updates in a single batch, producing a new immutable state. - */ - batch(state: TState, updates: Array>): TState; - - /** - * Convert internal state representation into a plain JS object. - */ - toPlainObject(state: TState): object; -} - -/** - * Snapshot wrapper — represents an immutable state value. - */ -export interface StateSnapshot { - readonly state: T; -} - -/** - * Create a new snapshot with the given initial state. - */ -export function create(initial: T): StateSnapshot; - -/** - * Get a value from a snapshot by path. - */ -export function get(snap: StateSnapshot, path: Path): R | undefined; - -/** - * Set a value in a snapshot by path. - * Returns a new snapshot with the updated state. - */ -export function set( - snap: StateSnapshot, - path: Path, - value: R -): StateSnapshot; - -/** - * @unsupported - * Apply a functional update to a snapshot. - * Useful for DSLs and producer-like patterns (similar to Immer). - * - * Example: - * ```ts - * const next = set(snap, draft => { - * draft.user.name = "Bob"; - * }); - * ``` - */ -export function set( - snap: StateSnapshot, - producer: (draft: T) => T -): StateSnapshot; - -/** - * Apply multiple updates in a single step. - * Returns a new snapshot with all updates applied. - */ -export function batch( - snap: StateSnapshot, - updates: Array> -): StateSnapshot; - -/** - * Convert a snapshot into a plain JS object. - */ -export function toPlainObject(snap: StateSnapshot): T; diff --git a/third-party/rigidify/src/layers/record/record.ts b/third-party/rigidify/src/layers/record/record.ts deleted file mode 100644 index 5b7fcce..0000000 --- a/third-party/rigidify/src/layers/record/record.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { isTypeCompatible } from "../utils/type_check"; - -const IMMUTABLE = true; - -class RecordLayer { - static #build(defaults: Record = {}, isMutable = IMMUTABLE) { - const _keys = Object.keys(defaults); - const _defaults = Object.create(null); - - for (const key of _keys) { - _defaults[key] = defaults[key]; - } - - return class StructRecord { - static fields = _keys; - static defaults = _defaults; - static isMutable = isMutable; - - static create(values: Record = {}) { - const obj: Record = Object.create(null); - - for (const key of _keys) { - const defaultKey = _defaults[key]; - const value = key in values ? values[key] : defaultKey; - - if (!isTypeCompatible(value, defaultKey)) { - throw new Error( - `Incompatible type for field "${key}": expected ${typeof defaultKey}, got ${typeof value}` - ); - } - - obj[key] = value; - } - - return Object.freeze(obj); - } - }; - } -} diff --git a/third-party/rigidify/src/layers/utils/type_check.ts b/third-party/rigidify/src/layers/utils/type_check.ts deleted file mode 100644 index c8da4a4..0000000 --- a/third-party/rigidify/src/layers/utils/type_check.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const typeOf = (value: unknown): string => { - // null -> array -> primitive - if (value === null) { - return "null"; // typeof null === "object" - } - - if (Array.isArray(value)) { - return "array"; - } - - return typeof value; // string | number | boolean | object | function | symbol | bigint | undefined -}; - -export const isTypeCompatible = (a: unknown, b: unknown): boolean => { - if (a === null || b === null) { - return a === b; - } - - const aIsArray = Array.isArray(a); - const bIsArray = Array.isArray(b); - - if (aIsArray || bIsArray) { - return aIsArray === bIsArray; - } - - return typeof a === typeof b; -}; - -export const isPrimitiveEqual = (a: unknown, b: unknown): boolean => { - if (a === b) { - return a !== 0 || 1 / (a as number) === 1 / (b as number); // distinguish +0 and -0 - } - - return a !== a && b !== b; // NaN === NaN -};