npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

belzium

v0.2.0-beta.0

Published

Framework frontend minimalista en TypeScript con compiler propio (.bel), reactividad, componentes, DI/IoC y pipelines de datos — cero dependencias de runtime.

Readme

Belzium (Beta)

Minimalist TypeScript frontend framework with its own compiler (.bel), reactivity, components, DI/IoC, data pipelines and a VSCode extension — zero runtime dependencies.

npm version npm downloads dependencies TypeScript tests license CI bundlephobia bundle size

Live demo

Install: npm install belzium — zero runtime dependencies.

Live demo: run npm run demo and open examples/demo.html in your browser.


English

Why Belzium?

Most apps are built around a fragmentation problem: data lives in a CSV, an API, a store or an event — and every screen maps, filters and aggregates it by hand, with ad-hoc glue. That glue is where bugs breed and where every change hurts. Belzium treats that as the problem to solve: one reactive model that flows from your data pipelines to your components, instead of three disconnected paradigms.

  • One mental model — refs, computed and effects are the same primitives in the UI and in the data plane.
  • Three pillars — reactive UI + components, DI/IoC, and engine-agnostic pipelines (batch or live, same definition).
  • Zero runtime dependencies — no framework tax for building a "simple" app.

Features

  • Reactivity (Vue-style):
    • reactive(): reactive proxies over objects and collections (Map, Set, arrays).
    • ref(): single reactive values with automatic unwrapping.
    • computed(): derived values with caching and lazy recalculation.
    • effect(), watch(), watchEffect(): effects that react to changes.
    • EffectScope / effectScope(): group and dispose effects together.
    • queueJob() / unqueueJob(): job queue with deduplication and microtask flush.
    • toRaw(), toReactive(): conversion between raw and reactive objects.
    • Scheduler with job queue and deduplication (flush: "sync" | "pre").
  • Components:
    • @Component(), @UI(): decorators for declaring components.
    • setup(props, { emit }): configuration function per component.
    • Public proxy with ref unwrapping.
    • provide() / inject(): values shared between ancestors and descendants.
    • useSlots(): slot content access.
    • input() / output(): reactive props and event emission to parent.
    • Lifecycle: onMounted(), onUnmounted(), onUpdated().
  • DI / IoC:
    • createApp(App).mount(...): runtime bootstrap with component mounting.
    • @Configuration() / @Bean() / @UI(): Spring-style declarative configuration (public barrel).
    • @Service(): marks a class as a managed component.
    • createApplication({ providers }): DI-only bootstrap.
    • ApplicationContext: register, registerProvider, registerComponent, createScope, resolve, has.
    • Scopes: SINGLETON (default), TRANSIENT, SCOPED.
      • Circular dependency detection and scope validation.
  • Stores (global reactive state):
    • @Store(): marks a class as a global non-IoC store.
    • useStore(StoreClass): returns a reactive singleton instance.
    • resetStores(): clears all live instances (useful in tests).
  • Hooks (reusable lifecycle logic):
    • @Hook(): marks a class as a component hook.
    • useHook(HookClass): creates a new instance per consuming component.
    • Its onMounted/onUnmounted methods bind to the consumer's lifecycle.
  • Custom Directives:
    • @Directive(): marks a class as a reusable template directive.
    • Usage in .bel: <Clickable enabled={...}>{...}</Clickable> compiles to h(Clickable, { enabled: ... }, [...]).
  • .bel Compiler → TypeScript:
    • JSX to h() / text() calls.
    • XML directives: <if> / <else> / <else-if> to ternary expressions.
    • <for each={item of items} key={key}> to .map() with keyed VNodes.
    • <switch value> / <case test> / <default> to IIFE with switch.
    • PascalCase custom components to components.
    • Auto-injection of runtime imports.
    • Typed expression layer with semantic roles (text, attrValue, eventHandler, spread, condition, iterable, key, discriminant, caseTest).
    • Structured errors (CompileError) with line:column and snippet.
    • Dashed attributes (data-*, aria-*, SVG) emitted correctly.
    • String literals allowed in <switch value> / <case test>.
    • Note: the .bel compiler is tooling (VSCode extension / build); it is not included in the npm runtime package.
  • VSCode Extension:
    • Syntax highlighting for .bel (decorators, directives, keywords).
    • IntelliSense: completions, hover, go-to-definition.
    • Diagnostics (syntactic + semantic).
    • Semantic tokens and folding ranges.
  • VNodes:
    • h(), text(), createTextVNode().
    • Virtual tree diffing and patching.
    • isSameVNode() for type + key comparison.

Requirements

  • Node.js 22+.
  • TypeScript with standard decorators (TC39).

Getting started

npm install
npm test          # runs the suite (537 tests / 50 files)
npm run demo      # builds the demo page (open examples/demo.html)

Examples

.bel component

import { ref } from "belzium";

@Component()
class Counter {
  count = ref(0);
  items = [1, 2, 3];

  render() {
    return (
      <div>
        <h1>Count: {this.count.value}</h1>
        <if condition={this.count.value >= 3}>
          <p>Big</p>
        </if>
        <else>
          <p>Small</p>
        </else>
        <ul>
          <for each={n of this.items} key={n}>
            <li>Item {n}</li>
          </for>
        </ul>
        <button onClick={() => this.count.value++}>+1</button>
      </div>
    );
  }
}

TypeScript equivalent

import { Component, ref, h, text, createApp } from "belzium";

@Component()
class Counter {
  count = ref(0);
  items = [1, 2, 3];

  render() {
    return h("div", null, [
      h("h1", null, [text(`Count: ${this.count.value}`)]),
      ...(this.count.value >= 3
        ? [h("p", null, [text("Big")])]
        : [h("p", null, [text("Small")])]),
      h("ul", null, [
        ...this.items.map((n) =>
          h("li", { key: n }, [text(`Item ${n}`)])
        ),
      ]),
      h("button", { onClick: () => this.count.value++ }, [text("+1")]),
    ]);
  }
}

createApp(Counter).mount(document.body);

Fluent ETL pipeline (CSV → aggregation)

import {
  Pipeline, source, runPipeline, reactiveEngine,
  getPipelineMetadata,
} from "belzium";

@Pipeline()
class SalesETL {
  raw = source.csv(`
    dept,amount
    ventas,10
    ventas,20
    it,5
  `);

  typed = this.raw.transform((row) => ({
    dept: row.dept as string,
    amount: Number(row.amount),
  }));

  byDept = this.typed.groupBy((row) => row.dept);

  totals = this.byDept.aggregate(
    (acc, row) => acc + row.amount,
    0,
  );

  report = this.totals.sink((row) => console.log(row));
}

// Batch: runs once and gives per-node partial outputs.
const result = runPipeline(SalesETL);

// Reactive (Pulses): the same definition runs live with the reactive engine.
const live = runPipeline(SalesETL, reactiveEngine);
// Inject new rows: invalidates only the affected branch and batches sinks.
const rawId = getPipelineMetadata(SalesETL)!.nodes.find((n) => n.type === "source")!.id;
live.ref(rawId).value = [
  { dept: "ventas", amount: 30 },
];

API

Reactivity

import { reactive, ref, computed, effect, watch, watchEffect, toRaw, isRef } from "belzium";

const state = reactive({ count: 0 });
const num = ref(0);
const doubled = computed(() => num.value * 2);

effect(() => console.log(state.count));
watch(() => state.count, (next, prev) => console.log(next, prev));
watchEffect(() => console.log(num.value));

Components

import {
  Component, createComponentInstance, setupComponent,
  getCurrentInstance, provide, inject, useSlots,
  onMounted, onUnmounted, onUpdated,
} from "belzium";

@Component()
class MyComponent {
  setup() {
    const theme = ref("dark");
    return { theme };
  }

  render() {
    return h("div", null, [text(this.theme.value)]);
  }
}

Pipelines

Typed node factories and both executors:

import {
  PipelineGraph, createSourceNode, createTransformNode, createSinkNode,
  LocalExecutor, ReactiveExecutor,
} from "belzium";

const graph = new PipelineGraph<number>();
const source = createSourceNode({ produce: () => [1, 2, 3] });
const doubled = createTransformNode({ map: (v) => v * 2 });
const sink = createSinkNode({ consume: (v) => console.log(v) });

graph.link(source, doubled);
graph.link(doubled, sink);

new LocalExecutor().execute(graph);

const live = new ReactiveExecutor().create(graph);
live.ref(source.id).value = [10, 20]; // re-runs the affected branch

Schema + sources layer:

import { createSchema, field, validateRows, parseCSV, fetchCSV, silver, gold } from "belzium";

const schema = createSchema([field("id", "number"), field("name", "string")]);
const rows = parseCSV("id,name\n1,ana\n2,leo");

validateRows(rows, schema);                     // { valid, errors }
const { clean, invalid } = silver(rows, schema); // splits invalid rows
gold(clean, { by: (r) => r.id, reduce: (acc) => acc + 1, seed: 0 }); // {key,value}[]

await fetchCSV("https://api.example.com/data.csv"); // rows ready to inject into a pulse

DI / IoC

import { Service, createApplication, ApplicationContext, createApp } from "belzium";

@Service()
class Logger {
  log(msg: string) { console.log(`[APP] ${msg}`); }
}

@Service({ dependencies: [Logger] })
class UserService {
  constructor(private logger: Logger) {}
  hello() { this.logger.log("Hello from Belzium"); }
}

// With createApplication (DI only):
const app = createApplication({ providers: [Logger, UserService] });
app.resolve(UserService).hello();

// With ApplicationContext:
const ctx = new ApplicationContext();
ctx.registerProvider({ useClass: UserService, dependencies: [Logger] });
ctx.resolve(UserService).hello();

// With createApp (runtime):
const runtime = createApp(MyRootComponent);
runtime.mount(document.body);

Stores

import { Store, useStore, resetStores, ref } from "belzium";

@Store()
class CounterStore {
  count = ref(0);
}

// In any component:
const store = useStore(CounterStore);
store.count.value++; // reactive, re-renders consuming components

// In tests:
resetStores();

Hooks

import { Hook, useHook, onMounted, ref } from "belzium";

@Hook()
class Timer {
  elapsed = ref(0);
  onMounted() { /* runs when the consuming component mounts */ }
  onUnmounted() { /* runs when it unmounts */ }
}

// Inside a component:
const timer = useHook(Timer);
console.log(timer.elapsed.value);

Directives

import { Directive, h, text } from "belzium";

@Directive()
class Clickable {
  props!: Readonly<{ enabled?: boolean }>;

  render() {
    return h("button", null, [text(String(this.props.enabled))]);
  }
}

Usage in .bel template: <Clickable enabled={this.isEnabled}><span>Click</span></Clickable>

VNodes

import { h, text, createTextVNode, isSameVNode } from "belzium";

const vnode = h("div", { class: "card" }, [
  text("Hello"),
  h("span", null, [text("World")]),
]);

isSameVNode(vnode, h("div", null, [])); // true (same type, no key)

.bel Compiler

The compiler transforms .bel files (TypeScript + JSX + Belzium directives) into valid TypeScript:

| .bel Syntax | Output | |---------------|--------| | <div> | h("div", null, [...]) | | <UserCard /> | h(UserCard, null, [...]) | | {expr} | text(String(expr)) | | <if condition={c}>...</if> <else>...</else> | ...((c) ? [...] : [...]) | | <for each={n of items} key={k}>...</for> | ...items.map((n) => h(..., { key: k }, [...])) | | <switch value={e}><case test={"v"}>...</case></switch> | IIFE with switch | | <Clickable enabled={p}>...</Clickable> | h(Clickable, { enabled: p }, [...]) |

The compiler models each {} JS snippet with a typed semantic role (text, attrValue, eventHandler, spread, condition, iterable, key, discriminant, caseTest), enabling correct emission and tooling. It accepts string literals in <switch value="a"> / <case test="b"> and emits dashed attributes (data-*, aria-*, SVG) as valid keys. Compilation errors are CompileError with line:column and snippet.

Usage within this repository (the compiler is tooling, not published to npm):

import { compile } from "./src/compiler";

const ts = compile(belSource, { importPath: "belzium" });

VSCode Extension

The .bel extension is located in tools/belzium-language/ (id bel).

Installation (recommended, from the repo root):

npm install                     # install monorepo deps (esbuild, typescript, ...)
npm run package:language        # build + package tools/belzium-language/belzium-language-0.1.1.vsix
code --install-extension tools/belzium-language/belzium-language-0.1.1.vsix --force

Or in one step: npm run install:language.

Note: after installing (or rebuilding) reload the VSCode window (Ctrl+Shift+P → Developer: Reload Window) so the extension activates. dist/ and types/ are build artifacts (gitignored): a fresh clone must run npm run package:language before the extension works.

Features:

  • TextMate syntax highlighting, including XML directives (<if>, <for>, <switch>, <case>, <default>, <else-if>, <else>) and decorators (@Component, @Store, ...) with dedicated colors.
  • Directive completions (<if>, <for>, <switch>, ...) and full IntelliSense.
  • Hover with inferred types.
  • Go-to-definition across .bel files.
  • Diagnostics (syntactic + semantic) with debounce.
  • Semantic tokens and folding ranges.
  • Debug: press F5 with the built-in "Run Belzium Extension" launch config (uses an Extension Development Host).

Vite Plugin

Dev-time compilation of .bel files (no JIT in the browser): the compiler runs inside the Vite dev server / build and emits plain h()/text() code, lowering standard decorators to es2020 and chaining source maps. Lives in tools/vite-plugin-belzium/.

// vite.config.ts (dev, while the plugin is private in this repo)
import { defineConfig } from "vite";
import belzium from "./tools/vite-plugin-belzium/dist/index.js";

export default defineConfig({
  plugins: [belzium()],
});

Then import { Hello } from "./hello.bel" just works. The package has zero runtime dependencies (dependencies: {}; peers: vite, esbuild, typescript, already present in any Vite/TS project). HMR v1 reloads the page on .bel edits.

Dev from this repo: npm run build:plugin, npm run test:plugin, npm run package:plugin (tarball dry-run).

Pipelines & Data Engineering

Pipelines are the second pillar of Belzium: an engine-agnostic data layer that reuses the same reactive core. The same definition runs as a batch job or live, by switching the engine.

  • Definition via @Pipeline() — discovers the nodes chained as class fields (source.csv(...).transform(...).groupBy(...).aggregate(...).sink(...)) and stores the graph in metadata. getPipelineMetadata(Clazz).graph runs with any PipelineEngine unchanged.
  • Swappable engines — runPipeline(Clazz) (batch, default localEngine) or runPipeline(Clazz, reactiveEngine) (live Pulses).
  • Nodes — typed factories createSourceNode…createSinkNode (source, transform, filter, join, group, aggregate, sink) plus a fluent API on every node (transform, filter, groupBy, aggregate, join, sink).
  • LocalExecutor — synchronous batch execution with topological order, cycle detection and per-node partial outputs.
  • ReactiveExecutor — live execution on top of Pulses: sources→writable ref, derived→computed, sinks→effects with batching; selective invalidation and dispose().
  • ETL / data engineering (src/etl, depends on the pipeline core — never the other way around):
    • Schema — createSchema()/field() + validateRows()/assertValidRows() with string | number | boolean | date | any types.
    • Sources — source.csv()/source.json()/source.from() (fluent builder), parseCSV()/parseJSON() (sync) and readCSVFile()/fetchCSV()/fetchJSON() (async), ready to inject rows into pulses.
    • Medallion — bronze() (raw ingest) → silver() (clean + validate → { clean, invalid }) → gold() (keyed aggregations).

Project structure

src/
  reactive/        Reactivity (proxies, refs, effects, scheduler, effect scopes)
  component/       Components (decorators, lifecycle, slots, I/O, hooks, directives)
  di/              DI/IoC (ApplicationContext, scopes, tokens, decorators)
  runtime/         Runtime (createApp, VNodes, renderers/patching)
  core/            Application (createApplication, bootstrap)
  pipeline/                Pipeline definition (nodes + graph), engine-agnostic
    decorator.ts   @Pipeline (discovers fluent nodes via class fields) + metadata
    graph.ts       PipelineGraph (node registration and linking)
    node.ts        createNode with fluent API / link / isPipelineNode
    run.ts         runPipeline (runs a @Pipeline with the engine you choose)
    nodes/         per-operation factories (source, transform, filter, join, group, aggregate, sink)
    execution/     shared operators + LocalExecutor (batch)
                   + ReactiveExecutor (Pulses) + engine.ts (local/reactive engines)
  etl/                     Data engineering: depends on the pipeline core (not vice versa)
    schema/        createSchema / field + validateRows / assertValidRows
    sources/       csv, json (sync) · file, http (async) · source (fluent builder)
    medallion/     bronze → silver → gold
  compiler.ts      `.bel` → TypeScript compiler (tooling for the VSCode extension; not shipped in the npm package)
  compiler/        astBuilder, codegen, templateLowering, expressionValidator, sourceMap, errors, nodes
  tsxTransform.ts  `.bel` → TSX transform for IDE support
  store.ts         @Store (global reactive state)
  application.ts   Public Application (runtime)
  applicationBootstrap.ts
  index.ts         Public API
tools/
  build-demo.mjs      Builds the demo page (compiles examples/.bel → examples/demo.bundle.js)
  belzium-language/  VSCode extension for `.bel` (id `bel`)
  vite-plugin-belzium/  Dev-time Vite plugin for `.bel` (compiles .bel in dev/build; zero runtime deps)
examples/            Usage examples (e.g. `basic-di.ts`) + demo.html (live demo page)
img/                 Logo and brand assets
tests/                537 tests (50 files)
docs/                Language spec + compiler architecture

Scripts

npm test                  # run tests
npm run typecheck         # verify types
npm run build             # build dist/ (ESM bundle + .d.ts types)
npm run demo              # build demo page: examples/demo.bundle.js (open examples/demo.html)
npm pack --dry-run        # inspect tarball contents
npm run build:types       # generate .d.ts files (VSCode extension)
npm run build:language    # build VSCode extension
npm run typecheck:language # verify extension types
npm run package:language  # build and package tools/belzium-language/*.vsix
npm run install:language  # package and install the extension in VSCode
npm run build:plugin     # build tools/vite-plugin-belzium/dist/index.js
npm run test:plugin      # unit + Vite-build integration tests for the plugin
npm run package:plugin   # tarball dry-run for the plugin
npm run test:e2e         # real-browser E2E (Playwright/Chromium) of the Vite demo
npm run e2e:install      # download the Playwright Chromium browser (first run)

Publishing:

npm login
npm publish --tag beta
git tag v0.1.0-beta.0 && git push --tags

Tests

npx vitest run

537 tests covering: reactivity, components, DI/IoC, stores, hooks, directives, pipelines (engines, fluent, schema, sources, medallion), compiler, vite plugin, language service.

The unit/integration suite runs on jsdom. On top of it, npm run test:e2e opens a real Chromium (Playwright) against the Vite demo and asserts the component renders and reacts to events in the DOM.

Roadmap & status

  • Current: 0.2.0-beta.0 — 537 tests passing. The public API is not yet frozen: until 1.0 you should expect breaking changes between releases.
  • Blocking 1.0:
    • Freeze the public API surface for reactivity, components and DI/IoC.
    • Stabilize the .bel compiler output across every supported template directive.
    • Set the stability contract for pipelines/ETL (schema + engines).
    • Formal release of the VSCode extension and the docs site.
  • Not on the radar: SSR, server components.

Contributing

Issues and PRs are welcome. See CONTRIBUTING.md for how to run the test suite, typecheck and build. MIT licensed — see LICENSE.


Español

¿Por qué Belzium?

La mayoría de apps nacen de un problema de fragmentación: los datos viven en un CSV, una API, un store o un evento — y cada pantalla los mapea, filtra y agrega a mano, con pegamento ad-hoc. Ese pegamento es donde se crían los bugs y donde cada cambio duele. Belzium encara eso como el problema a resolver: un único modelo reactivo que fluye de tus pipelines de datos a tus componentes, en vez de tres paradigmas desconectados.

  • Un solo modelo mental — los ref, computed y efectos son los mismos primitivos en la UI y en la capa de datos.
  • Tres pilares — UI reactiva + componentes, DI/IoC y pipelines agnósticos al motor (batch o en vivo, misma definición).
  • Cero dependencias de runtime — sin impuesto de framework para una app "simple".

Características

  • Reactividad (estilo Vue):
    • reactive(): proxies reactivos sobre objetos y colecciones (Map, Set, arrays).
    • ref(): variables reactivas individuales con desempaquetado automático.
    • computed(): valores derivados con caché y recálculo perezoso.
    • effect(), watch(), watchEffect(): efectos que reaccionan a los cambios.
    • EffectScope / effectScope(): agrupa y detiene efectos de forma conjunta.
    • queueJob() / unqueueJob(): cola de jobs con deduplicación y flush por microtask.
    • toRaw(), toReactive(): conversión entre objetos crudos y reactivos.
    • Scheduler con cola de jobs y deduplicación (flush: "sync" | "pre").
  • Componentes:
    • @Component(), @UI(): decoradores para declarar componentes.
    • setup(props, { emit }): función de configuración de cada componente.
    • Proxy público con desempaquetado de ref.
    • provide() / inject(): valores compartidos entre ancestros y descendientes.
    • useSlots(): acceso al contenido de slots.
    • input() / output(): props reactivas y emisión de eventos al padre.
    • Lifecycle: onMounted(), onUnmounted(), onUpdated().
  • DI / IoC:
    • createApp(App).mount(...): arranque del runtime con montaje de componentes.
    • @Configuration() / @Bean() / @UI(): configuración declarativa estilo Spring (barrel público).
    • @Service(): marca una clase como componente gestionado.
    • createApplication({ providers }): arranque solo-DI.
    • ApplicationContext: register, registerProvider, registerComponent, createScope, resolve, has.
    • Scopes: SINGLETON (por defecto), TRANSIENT, SCOPED.
      • Detección de dependencias circulares y validación de scope.
  • Stores (estado global reactivo):
    • @Store(): marca una clase como store global sin IoC.
    • useStore(StoreClass): retorna una instancia singleton reactiva.
    • resetStores(): limpia todas las instancias (útil en tests).
  • Hooks (lógica reutilizable):
    • @Hook(): marca una clase como hook de componentes.
    • useHook(HookClass): crea una instancia nueva por componente consumidor.
    • Sus métodos onMounted/onUnmounted se enlazan al ciclo de vida del componente.
  • Directivas personalizadas:
    • @Directive(): marca una clase como directiva reutilizable en templates.
    • Uso en .bel: <Clickable enabled={...}>{...}</Clickable> compila a h(Clickable, { enabled: ... }, [...]).
  • Compiler .bel → TypeScript:
    • JSX a llamadas h() / text().
    • Directivas XML: <if> / <else> / <else-if> a expresiones ternarias.
    • <for each={item of items} key={key}> a .map() con VNodes keyados.
    • <switch value> / <case test> / <default> a IIFE con switch.
    • Componentes custom PascalCase a componentes.
    • Auto-inyección de imports del runtime.
    • Capa de expresiones tipadas con roles semánticos (text, attrValue, eventHandler, spread, condition, iterable, key, discriminant, caseTest).
    • Errores estructurados (CompileError) con línea:columna y snippet.
    • Atributos con guion (data-*, aria-*, SVG) emitidos correctamente.
    • Literales string admitidos en <switch value> / <case test>.
    • Nota: el compiler .bel es tooling (extensión VSCode / build); no está incluido en el paquete npm (runtime).
  • Extensión VSCode:
    • Syntax highlighting para .bel (decoradores, directivas, keywords).
    • IntelliSense: completions, hover, go-to-definition.
    • Diagnostics (syntactic + semantic).
    • Semantic tokens y folding ranges.
  • VNodes:
    • h(), text(), createTextVNode().
    • Diffing y patching de árbol virtual.
    • isSameVNode() para comparación por tipo + key.

Requisitos

  • Node.js 22+.
  • TypeScript con decoradores estándar (TC39).

Empezar rápido

npm install
npm test          # ejecuta la suite (537 tests / 50 archivos)
npm run demo      # genera la página demo (abrir examples/demo.html)

Ejemplos

Componente .bel

import { ref } from "belzium";

@Component()
class Counter {
  count = ref(0);
  items = [1, 2, 3];

  render() {
    return (
      <div>
        <h1>Conteo: {this.count.value}</h1>
        <if condition={this.count.value >= 3}>
          <p>Grande</p>
        </if>
        <else>
          <p>Pequeño</p>
        </else>
        <ul>
          <for each={n of this.items} key={n}>
            <li>Item {n}</li>
          </for>
        </ul>
        <button onClick={() => this.count.value++}>+1</button>
      </div>
    );
  }
}

Equivalente en TypeScript

import { Component, ref, h, text, createApp } from "belzium";

@Component()
class Counter {
  count = ref(0);
  items = [1, 2, 3];

  render() {
    return h("div", null, [
      h("h1", null, [text(`Conteo: ${this.count.value}`)]),
      ...(this.count.value >= 3
        ? [h("p", null, [text("Grande")])]
        : [h("p", null, [text("Pequeño")])]),
      h("ul", null, [
        ...this.items.map((n) =>
          h("li", { key: n }, [text(`Item ${n}`)])
        ),
      ]),
      h("button", { onClick: () => this.count.value++ }, [text("+1")]),
    ]);
  }
}

createApp(Counter).mount(document.body);

Pipeline ETL fluido (CSV → agregación)

import {
  Pipeline, source, runPipeline, reactiveEngine,
  getPipelineMetadata,
} from "belzium";

@Pipeline()
class SalesETL {
  raw = source.csv(`
    dept,amount
    ventas,10
    ventas,20
    it,5
  `);

  typed = this.raw.transform((row) => ({
    dept: row.dept as string,
    amount: Number(row.amount),
  }));

  byDept = this.typed.groupBy((row) => row.dept);

  totals = this.byDept.aggregate(
    (acc, row) => acc + row.amount,
    0,
  );

  report = this.totals.sink((row) => console.log(row));
}

// Batch: ejecuta una vez y entrega outputs parciales por nodo.
const result = runPipeline(SalesETL);

// Reactivo (Pulses): la misma definición corre en vivo con el motor reactivo.
const live = runPipeline(SalesETL, reactiveEngine);
// Inyectar nuevas filas: invalida solo la rama afectada y consolida los sinks.
const rawId = getPipelineMetadata(SalesETL)!.nodes.find((n) => n.type === "source")!.id;
live.ref(rawId).value = [
  { dept: "ventas", amount: 30 },
];

API

Reactividad

import { reactive, ref, computed, effect, watch, watchEffect, toRaw, isRef } from "belzium";

const state = reactive({ count: 0 });
const num = ref(0);
const doubled = computed(() => num.value * 2);

effect(() => console.log(state.count));
watch(() => state.count, (next, prev) => console.log(next, prev));
watchEffect(() => console.log(num.value));

Componentes

import {
  Component, createComponentInstance, setupComponent,
  getCurrentInstance, provide, inject, useSlots,
  onMounted, onUnmounted, onUpdated,
} from "belzium";

@Component()
class MyComponent {
  setup() {
    const theme = ref("dark");
    return { theme };
  }

  render() {
    return h("div", null, [text(this.theme.value)]);
  }
}

Pipelines

Nodos con factories tipadas y ejecución con los dos motores:

import {
  PipelineGraph, createSourceNode, createTransformNode, createSinkNode,
  LocalExecutor, ReactiveExecutor,
} from "belzium";

const graph = new PipelineGraph<number>();
const source = createSourceNode({ produce: () => [1, 2, 3] });
const doubled = createTransformNode({ map: (v) => v * 2 });
const sink = createSinkNode({ consume: (v) => console.log(v) });

graph.link(source, doubled);
graph.link(doubled, sink);

new LocalExecutor().execute(graph);

const live = new ReactiveExecutor().create(graph);
live.ref(source.id).value = [10, 20]; // re-procesa la rama afectada

La capa Schema + fuentes:

import { createSchema, field, validateRows, parseCSV, fetchCSV, silver, gold } from "belzium";

const schema = createSchema([field("id", "number"), field("name", "string")]);
const rows = parseCSV("id,name\n1,ana\n2,leo");

validateRows(rows, schema);                     // { valid, errors }
const { clean, invalid } = silver(rows, schema); // separa inválidas
gold(clean, { by: (r) => r.id, reduce: (acc) => acc + 1, seed: 0 }); // {key,value}[]

await fetchCSV("https://api.example.com/data.csv"); // filas listas para inyectar a un pulse

DI / IoC

import { Service, createApplication, ApplicationContext, createApp } from "belzium";

@Service()
class Logger {
  log(msg: string) { console.log(`[APP] ${msg}`); }
}

@Service({ dependencies: [Logger] })
class UserService {
  constructor(private logger: Logger) {}
  hello() { this.logger.log("Hola desde Belzium"); }
}

// Con createApplication (solo DI):
const app = createApplication({ providers: [Logger, UserService] });
app.resolve(UserService).hello();

// Con ApplicationContext:
const ctx = new ApplicationContext();
ctx.registerProvider({ useClass: UserService, dependencies: [Logger] });
ctx.resolve(UserService).hello();

// Con createApp (runtime):
const runtime = createApp(MyRootComponent);
runtime.mount(document.body);

Stores

import { Store, useStore, resetStores, ref } from "belzium";

@Store()
class CounterStore {
  count = ref(0);
}

// En cualquier componente:
const store = useStore(CounterStore);
store.count.value++; // reactiva, re-renderiza componentes que la lean

// En tests:
resetStores();

Hooks

import { Hook, useHook, onMounted, ref } from "belzium";

@Hook()
class Timer {
  elapsed = ref(0);
  onMounted() { /* se ejecuta al montar el componente consumidor */ }
  onUnmounted() { /* se ejecuta al desmontar */ }
}

// Dentro de un componente:
const timer = useHook(Timer);
console.log(timer.elapsed.value);

Directivas

import { Directive, h, text } from "belzium";

@Directive()
class Clickable {
  props!: Readonly<{ enabled?: boolean }>;

  render() {
    return h("button", null, [text(String(this.props.enabled))]);
  }
}

Uso en template .bel: <Clickable enabled={this.isEnabled}><span>Click</span></Clickable>

VNodes

import { h, text, createTextVNode, isSameVNode } from "belzium";

const vnode = h("div", { class: "card" }, [
  text("Hello"),
  h("span", null, [text("World")]),
]);

isSameVNode(vnode, h("div", null, [])); // true (mismo tipo, sin key)

Compiler .bel

El compiler transforma archivos .bel (TypeScript + JSX + directivas Belzium) en TypeScript válido:

| Sintaxis .bel | Salida | |-----------------|--------| | <div> | h("div", null, [...]) | | <UserCard /> | h(UserCard, null, [...]) | | {expr} | text(String(expr)) | | <if condition={c}>...</if> <else>...</else> | ...((c) ? [...] : [...]) | | <for each={n of items} key={k}>...</for> | ...items.map((n) => h(..., { key: k }, [...])) | | <switch value={e}><case test={"v"}>...</case></switch> | IIFE con switch | | <Clickable enabled={p}>...</Clickable> | h(Clickable, { enabled: p }, [...]) |

El compiler modela cada fragmento JS de {} con un rol semántico tipado (text, attrValue, eventHandler, spread, condition, iterable, key, discriminant, caseTest), lo que permite emisión correcta y tooling. Acepta literales string en <switch value="a"> / <case test="b"> y emite atributos con guion (data-*, aria-*, SVG) como claves válidas. Los errores de compilación son CompileError con línea:columna y snippet.

Uso dentro de este repositorio (el compiler es tooling, no se publica en npm):

import { compile } from "./src/compiler";

const ts = compile(belSource, { importPath: "belzium" });

Extensión VSCode

La extensión para .bel se encuentra en tools/belzium-language/ (id bel).

Instalación (recomendada, desde la raíz del repo):

npm install                     # instalar deps del monorepo (esbuild, typescript, ...)
npm run package:language        # compila y genera tools/belzium-language/belzium-language-0.1.1.vsix
code --install-extension tools/belzium-language/belzium-language-0.1.1.vsix --force

O en un solo paso: npm run install:language.

Nota: tras instalar (o recompilar), recarga la ventana de VSCode (Ctrl+Shift+P → Developer: Reload Window) para que la extensión se active. dist/ y types/ son artefactos de build (gitignored): un clon fresco necesita ejecutar npm run package:language antes de usar la extensión.

Features:

  • Syntax highlighting con TextMate grammar, incluidas las directivas XML (<if>, <for>, <switch>, <case>, <default>, <else-if>, <else>) y los decoradores (@Component, @Store, ...) con colores propios.
  • Completions de directivas (<if>, <for>, <switch>, ...) e IntelliSense completo.
  • Hover con tipo inferido.
  • Go-to-definition entre archivos .bel.
  • Diagnostics (syntactic + semantic) con debounce.
  • Semantic tokens y folding ranges.
  • Debug: F5 con la configuración "Run Belzium Extension" ya incluida (usa Extension Development Host).

Plugin Vite

Compilación dev-time de archivos .bel (sin JIT en el navegador): el compiler corre dentro del dev server / build de Vite y emite código h()/text() plano, bajando los decoradores estándar a es2020 y encadenando source maps. Vive en tools/vite-plugin-belzium/.

// vite.config.ts (dev, mientras el plugin es privado en este repo)
import { defineConfig } from "vite";
import belzium from "./tools/vite-plugin-belzium/dist/index.js";

export default defineConfig({
  plugins: [belzium()],
});

Luego import { Hello } from "./hello.bel" funciona directo. El paquete tiene cero dependencias de runtime (dependencies: {}; peers: vite, esbuild, typescript, ya presentes en cualquier proyecto Vite/TS). HMR v1 recarga la página al editar un .bel.

Desde el repo: npm run build:plugin, npm run test:plugin, npm run package:plugin (tarball dry-run).

Pipelines e ingeniería de datos

Los pipelines son el segundo pilar de Belzium: una capa de datos agnóstica al motor que reutiliza el mismo core reactivo. La misma definición corre como job batch o en vivo, solo cambiando el motor.

  • Definición con @Pipeline() — descubre los nodos encadenados como campos de la clase (source.csv(...).transform(...).groupBy(...).aggregate(...).sink(...)) y guarda el grafo en metadata. getPipelineMetadata(Clase).graph se ejecuta con cualquier PipelineEngine sin cambios.
  • Motores intercambiables — runPipeline(Clase) (batch, por defecto localEngine) o runPipeline(Clase, reactiveEngine) (Pulses en vivo).
  • Nodos — factories tipadas createSourceNode…createSinkNode (source, transform, filter, join, group, aggregate, sink) y API fluido sobre cada nodo (transform, filter, groupBy, aggregate, join, sink).
  • LocalExecutor — ejecución batch, síncrona, con orden topológico, detección de ciclos y outputs parciales por nodo.
  • ReactiveExecutor — ejecución viva sobre Pulses: sources→ref escribible, derivados→computed, sinks→efectos con batching; invalidación selectiva y dispose().
  • ETL / ingeniería de datos (src/etl, depende del core del pipeline — nunca al revés):
    • Schema — createSchema()/field() + validateRows()/assertValidRows() con tipos string | number | boolean | date | any.
    • Fuentes — source.csv()/source.json()/source.from() (builder fluido), parseCSV()/parseJSON() (sync) y readCSVFile()/fetchCSV()/fetchJSON() (async), listas para inyectar filas a los pulses.
    • Medallion — bronze() (ingesta cruda) → silver() (limpieza + validación → { clean, invalid }) → gold() (agregaciones por clave).

Estructura del proyecto

src/
  reactive/        Reactividad (proxies, refs, efectos, scheduler, effect scopes)
  component/       Componentes (decoradores, lifecycle, slots, I/O, hooks, directives)
  di/              DI/IoC (ApplicationContext, scopes, tokens, decorators)
  runtime/         Runtime (createApp, VNodes, renderers/patching)
  core/            Application (createApplication, bootstrap)
  pipeline/                Definición del pipeline (nodos + grafo), agnóstica al motor
    decorator.ts   @Pipeline (descubre los nodos fluyentes por campos) + metadata
    graph.ts       PipelineGraph (registro y conexión de nodos)
    node.ts        createNode con API fluido / link / isPipelineNode
    run.ts         runPipeline (ejecuta una @Pipeline con el motor que elijas)
    nodes/         factories por operación (source, transform, filter, join, group, aggregate, sink)
    execution/     operadores compartidos + LocalExecutor (batch)
                   + ReactiveExecutor (Pulses) + engine.ts (motores: local/reactive)
  etl/                     Ingeniería de datos: depende del core del pipeline (no al revés)
    schema/        createSchema / field + validateRows / assertValidRows
    sources/       csv, json (sync) · file, http (async) · source (builder fluido)
    medallion/     bronze → silver → gold
  compiler.ts      Compiler .bel → TypeScript (tooling de la extensión VSCode; no va al paquete npm)
  compiler/        astBuilder, codegen, templateLowering, expressionValidator, sourceMap, errors, nodes
  tsxTransform.ts  Transform .bel → TSX para soporte IDE
  store.ts         @Store (estado global reactivo)
  application.ts   Application pública (runtime)
  applicationBootstrap.ts
  index.ts         API pública
tools/
  build-demo.mjs      Genera la página demo (compila examples/.bel → examples/demo.bundle.js)
  belzium-language/  Extensión VSCode para `.bel` (id `bel`)
  vite-plugin-belzium/  Plugin Vite dev-time para `.bel` (compila .bel en dev/build; cero deps de runtime)
examples/            Ejemplos de uso (p. ej. `basic-di.ts`) + demo.html (página demo en vivo)
img/                 Logo y recursos de marca
tests/                537 tests (50 archivos)
docs/                Language spec + compiler architecture

Scripts

npm test                  # ejecutar tests
npm run typecheck         # verificar tipos
npm run build             # generar dist/ (JS bundle + tipos .d.ts)
npm run demo              # generar la página demo: examples/demo.bundle.js (abrir examples/demo.html)
npm pack --dry-run        # inspeccionar el contenido del tarball
npm run build:types       # generar archivos .d.ts (extensión VSCode)
npm run build:language    # compilar extensión VSCode
npm run typecheck:language # verificar tipos de la extensión
npm run package:language  # compilar y empaquetar tools/belzium-language/*.vsix
npm run install:language  # empaquetar e instalar la extensión en VSCode
npm run build:plugin     # compilar tools/vite-plugin-belzium/dist/index.js
npm run test:plugin      # tests unit + integración (vite build) del plugin
npm run package:plugin   # tarball dry-run del plugin
npm run test:e2e         # E2E en navegador real (Playwright/Chromium) de la demo Vite
npm run e2e:install      # descargar el navegador Chromium de Playwright (primera vez)

Publicación:

npm login
npm publish --tag beta
git tag v0.1.0-beta.0 && git push --tags

Tests

npx vitest run

537 tests cubriendo: reactividad, componentes, DI/IoC, stores, hooks, directivas, pipelines (motores, fluido, schema, fuentes, medallion), compiler, plugin vite, language service.

La suite unit/integración corre sobre jsdom. Encima, npm run test:e2e abre un Chromium real (Playwright) contra la demo de Vite y verifica que el componente se renderiza y reacciona a eventos en el DOM.

Roadmap y estado

  • Actual: 0.2.0-beta.0 — 537 tests en verde. La API pública aún no está congelada: hasta 1.0 espera cambios que rompen compatibilidad entre releases.
  • Bloqueantes para 1.0:
    • Congelar la superficie de API pública de reactividad, componentes y DI/IoC.
    • Estabilizar la salida del compiler .bel en todas las directivas soportadas.
    • Fijar el contrato de estabilidad de pipelines/ETL (schema + motores).
    • Release formal de la extensión VSCode y el sitio de docs.
  • Fuera del roadmap: SSR, server components.

Contribución

Issues y PRs bienvenidos. Mira CONTRIBUTING.md para correr tests, typecheck y build. Licencia MIT — ver LICENSE.