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