npm package: @lithium-ts/core
Description: A lightweight, modern web framework built on top of [Lit](https://lit.dev) with reactive state management, dependency injection, and powerful routing.
Latest version: 0.1.0

# 🔋 LithiumJS

A lightweight, modern web framework built on top of [Lit](https://lit.dev) with reactive state management, dependency injection, and powerful routing.

## Table of Contents

- [Installation](#installation)
- [Core Concepts](#core-concepts)
  - [LithiumElement](#lithiumelement)
  - [Pages](#pages)
  - [Modules](#modules)
  - [LithiumApp](#lithiumapp)
- [State Management (EventBus)](#state-management-eventbus)
  - [Channels](#channels)
  - [Storage Types](#storage-types)
  - [Instant Events](#instant-events)
  - [Middleware](#middleware)
- [Routing](#routing)
  - [LithiumRouter](#lithiumrouter)
  - [Route Guards](#route-guards)
  - [Programmatic Navigation](#programmatic-navigation)
- [Decorators](#decorators)
  - [@defer](#defer)
  - [@lazy](#lazy)
  - [@conditional](#conditional)
- [Services & Dependency Injection](#services--dependency-injection)
  - [Creating Services](#creating-services)
  - [HTTP Methods](#http-methods)
  - [Interceptors](#interceptors)
- [WebSocket Services](#websocket-services)
- [API Reference](#api-reference)

---

## Installation

```bash
npm install @lithium-ts/core
```

---

## Core Concepts

### LithiumElement

The base class for all components. Extends Lit's `LitElement` with reactive signals and EventBus integration.

```typescript
import { LithiumElement, defineElement, html } from "@lithium-ts/core";

@defineElement({ tag: "my-button" })
export class MyButton extends LithiumElement {
  render() {
    return html`<button><slot></slot></button>`;
  }
}
```

#### Key Methods

| Method                      | Description                          |
| --------------------------- | ------------------------------------ |
| `channel(name, config)`     | Create/access a reactive channel     |
| `publish(name, data)`       | Publish data to a channel            |
| `subscribe(name, callback)` | Subscribe to channel changes         |
| `emit(event, data)`         | Emit instant event (fire-and-forget) |
| `on(event, callback)`       | Listen to instant events             |
| `navigate(path, options)`   | Navigate to a route                  |
| `output(event, detail)`     | Emit a DOM CustomEvent               |
| `clear(name)`               | Remove a channel                     |
| `clearAll(storage?)`        | Remove all channels                  |

---

### Pages

Pages are components with automatic title management. The tag name is auto-generated from the class name.

```typescript
import { LithiumElement, definePage, html } from "@lithium-ts/core";
import styles from "./home.page.css?inline";

@definePage({
  title: "Home - My App",
  styles: [styles],
})
export class HomePage extends LithiumElement {
  render() {
    return html`
      <h1>Welcome!</h1>
      <button @click=${() => this.navigate("/about")}>Go to About</button>
    `;
  }
}
// Auto-registered as <home-page>
```

---

### Modules

Modules group routes with optional layouts. Perfect for feature modules (admin, auth, etc.).

```typescript
import { LithiumModule, defineModule, html } from "@lithium-ts/core";

const adminRoutes = [
  { path: "/", render: () => html`<admin-dashboard></admin-dashboard>` },
  { path: "/users", render: () => html`<admin-users></admin-users>` },
];

@defineModule({ routes: adminRoutes })
export class AdminModule extends LithiumModule {
  // Optional: Custom layout
  render() {
    return html`
      <nav>Admin Navigation</nav>
      <main>${this._routes.outlet()}</main>
    `;
  }
}
// Auto-registered as <admin-module>
```

---

### LithiumApp

The root application component with main routing.

```typescript
import { LithiumApp, defineApp, html } from "@lithium-ts/core";

const routes = [
  { path: "/", render: () => html`<home-page></home-page>` },
  { path: "/about", render: () => html`<about-page></about-page>` },
  { path: "/admin/*", render: () => html`<admin-module></admin-module>` },
];

@defineApp({
  tag: "main-app",
  routes,
})
export class MainApp extends LithiumApp {
  render() {
    return html`
      <header>My App</header>
      ${this._router.outlet()}
      <footer>© 2026</footer>
    `;
  }
}
```

---

## State Management (EventBus)

LithiumJS uses a reactive EventBus with Signals for state management.

### Channels

Channels are reactive state containers that automatically update components.

```typescript
// In any component
export class MyComponent extends LithiumElement {
  // Create a channel (reactive!)
  private user = this.channel<User>("user:current", {
    initialValue: null,
    storage: "session",
  });

  render() {
    const user = this.user.get(); // Reactive - auto re-renders on change
    return html`<p>Hello, ${user?.name ?? "Guest"}</p>`;
  }

  login(userData: User) {
    this.publish("user:current", userData); // All subscribers update
  }
}
```

#### Channel Configuration

```typescript
this.channel<T>("channel-name", {
  // Value
  initialValue: null,

  // Storage type
  storage: "memory" | "session" | "local" | "page",
  storageKey: "custom-key", // Default: lithium:channel-name

  // Auto-cleanup
  ttl: 60000, // Time-to-live in ms
  autoCleanup: true, // Delete when no subscribers

  // For 'page' storage only
  persist: true, // Survive page refresh

  // Lifecycle hooks
  onChange: (newVal, oldVal) => console.log("Changed"),
  onInit: (value) => console.log("First value set"),
  onClear: () => console.log("Channel cleared"),

  // Transformation
  validate: (value) => value.id > 0, // Return false to cancel update
  transform: (value) => normalize(value),

  // Rate limiting
  debounce: 500, // Debounce onChange
  throttle: 1000, // Throttle onChange
});
```

---

### Storage Types

| Type      | Persistence          | Use Case                |
| --------- | -------------------- | ----------------------- |
| `memory`  | Lost on refresh      | Temporary UI state      |
| `session` | Browser tab lifetime | User session data       |
| `local`   | Forever              | User preferences        |
| `page`    | Until route change   | Form drafts, page state |

#### Page Storage Example

```typescript
// Form data that clears when navigating away
private formDraft = this.channel('form:draft', {
  initialValue: { name: '', email: '' },
  storage: 'page',
  persist: false  // Clears on navigation
});

// Notes that survive navigation but still page-scoped
private notes = this.channel('page:notes', {
  initialValue: '',
  storage: 'page',
  persist: true  // Survives navigation, stored in sessionStorage
});
```

---

### Instant Events

Fire-and-forget events without persistence.

```typescript
// Emit (sender)
this.emit('toast:show', {
  message: 'Saved!',
  type: 'success'
});

// Listen (receiver)
connectedCallback() {
  super.connectedCallback();
  this.on('toast:show', (data) => {
    this.showToast(data.message, data.type);
  });
}
```

---

### Middleware

Intercept all EventBus actions for logging, validation, etc.

```typescript
import { EventBus } from "@lithium-ts/core";

// Add middleware
EventBus.use((action, channelOrEvent, data, next) => {
  console.log(`[${action}] ${channelOrEvent}:`, data);
  next(); // Must call next() to continue
});

// Enable dev mode for detailed logging
EventBus.setDevMode(true);
```

---

## Routing

### LithiumRouter

A wrapper component that intercepts navigation for guards.

```typescript
import { LithiumApp, defineApp, html } from "@lithium-ts/core";

@defineApp({ tag: "main-app", routes })
export class MainApp extends LithiumApp {
  render() {
    return html`
      <lithium-router
        .beforeRoute=${this.authGuard}
        .afterRoute=${this.analytics}
      >
        ${this._router.outlet()}
      </lithium-router>
    `;
  }
}
```

---

### Route Guards

Guards control access to routes.

#### beforeRoute

Runs before navigation. Can block or redirect.

```typescript
import { RouteLocation, RouteGuardResult } from "@lithium-ts/core";

const authGuard = async (
  to: RouteLocation,
  from: RouteLocation,
): Promise<boolean | RouteGuardResult> => {
  const isAuthenticated = !!localStorage.getItem("token");

  // Protected routes
  if (to.path.startsWith("/admin") && !isAuthenticated) {
    return { continue: false, redirect: "/login" };
  }

  // Allow navigation
  return true;
};
```

#### afterRoute

Runs after successful navigation. For analytics, logging, etc.

```typescript
const analyticsGuard = (to: RouteLocation, from: RouteLocation) => {
  analytics.track("page_view", {
    path: to.path,
    referrer: from.path,
  });
};
```

#### RouteLocation Object

```typescript
interface RouteLocation {
  path: string; // /users/123
  query: Record<string, string>; // { tab: 'settings' }
  hash: string; // 'section-1'
  fullPath: string; // /users/123?tab=settings#section-1
}
```

---

### Programmatic Navigation

Navigate from anywhere using static methods or component methods.

```typescript
import { LithiumRouter } from "@lithium-ts/core";

// From anywhere (static)
LithiumRouter.navigate("/dashboard");
LithiumRouter.navigate("/search", { query: { q: "hello" } });
LithiumRouter.navigate("/docs", { hash: "installation" });
LithiumRouter.navigate("/login", { replace: true }); // No history entry
LithiumRouter.back();
LithiumRouter.forward();
LithiumRouter.go(-2);

// From a component
class MyComponent extends LithiumElement {
  goToDashboard() {
    this.navigate("/dashboard");
    this.navigate("/profile", { state: { from: "home" } });
  }
}
```

---

## Decorators

### @defer

Loads content after initial render. Perfect for non-critical UI.

```typescript
import { defer, html } from "@lithium-ts/core";

class MyPage extends LithiumElement {
  // Basic - loads on next microtask
  @defer()
  get analytics() {
    return html`<analytics-widget></analytics-widget>`;
  }

  // With placeholder
  @defer({ placeholder: html`<loading-spinner></loading-spinner>` })
  get charts() {
    return html`<heavy-charts></heavy-charts>`;
  }

  // With delay
  @defer({ delay: 2000 })
  get lowPriority() {
    return html`<recommendations></recommendations>`;
  }

  // With dynamic import (real code-splitting!)
  @defer({
    loader: () => import("./heavy-component.js"),
    placeholder: html`<skeleton-loader></skeleton-loader>`,
  })
  get heavyContent() {
    return html`<heavy-component></heavy-component>`;
  }

  render() {
    return html`
      <main>Important content first</main>
      ${this.analytics} ${this.charts}
    `;
  }
}
```

---

### @lazy

Loads content when it enters the viewport using IntersectionObserver.

```typescript
import { lazy, html } from "@lithium-ts/core";

class MyPage extends LithiumElement {
  // Basic lazy loading
  @lazy()
  get comments() {
    return html`<comments-section></comments-section>`;
  }

  // With options
  @lazy({
    threshold: 0.5, // 50% visible
    rootMargin: "100px", // Load 100px before visible
    placeholder: html`<skeleton></skeleton>`,
    minHeight: "200px", // Prevent layout shift
  })
  get heavySection() {
    return html`<data-visualization></data-visualization>`;
  }

  // With dynamic import
  @lazy({
    loader: () => import("./gallery.js"),
    placeholder: html`<gallery-skeleton></gallery-skeleton>`,
  })
  get gallery() {
    return html`<photo-gallery></photo-gallery>`;
  }

  render() {
    return html`
      <main>Above the fold</main>
      <div style="height: 200vh;">Scroll down...</div>
      ${this.comments}
      <!-- Loads when scrolled into view -->
      ${this.gallery}
    `;
  }
}
```

---

### @conditional

Renders content only when a condition is true. Supports lazy loading.

```typescript
import { conditional, html } from "@lithium-ts/core";
import { state } from "lit/decorators.js";

class MyPage extends LithiumElement {
  @state() isAdmin = false;
  @state() showAdvanced = false;

  // Basic conditional
  @conditional("isAdmin")
  get adminPanel() {
    return html`<admin-panel></admin-panel>`;
  }

  // With placeholder
  @conditional("showAdvanced", {
    placeholder: html`<p>Enable advanced mode to see more options</p>`,
  })
  get advancedOptions() {
    return html`<advanced-settings></advanced-settings>`;
  }

  // With lazy loading (only imports when condition is true!)
  @conditional("isAdmin", {
    loader: () => import("./admin-dashboard.js"),
    placeholder: html`<p>Admin only</p>`,
  })
  get adminDashboard() {
    return html`<admin-dashboard></admin-dashboard>`;
  }

  render() {
    return html`
      <main>${this.adminPanel}</main>
      <div>${this.advancedOptions}</div>
      <button @click=${() => (this.isAdmin = !this.isAdmin)}>
        Toggle Admin
      </button>
    `;
  }
}
```

---

## Services & Dependency Injection

### Creating Services

Services are singletons with HTTP helpers and EventBus integration.

```typescript
import {
  LithiumService,
  service,
  inject,
  ServiceInterceptor,
} from "@lithium-ts/core";

// Optional: Service-specific interceptor
const cacheInterceptor: ServiceInterceptor = {
  name: "cache",
  response: (res, data) => {
    localStorage.setItem(`cache:${res.url}`, JSON.stringify(data));
    return data;
  },
};

@service({
  baseUrl: "https://api.example.com",
  headers: { "X-App-Version": "1.0.0" },
  interceptors: [cacheInterceptor],
})
export class UserService extends LithiumService {
  async getUsers(): Promise<User[]> {
    return this.get<User[]>("/users", {
      onSuccess: "users:loaded",
      onError: "users:error",
    });
  }

  async createUser(user: User): Promise<User> {
    return this.post<User>("/users", user);
  }

  async updateUser(id: number, data: Partial<User>): Promise<User> {
    return this.put<User>(`/users/${id}`, data);
  }

  async deleteUser(id: number): Promise<void> {
    return this.delete(`/users/${id}`);
  }
}
```

### Using Services in Components

```typescript
import { inject } from "@lithium-ts/core";

@definePage({ title: "Users" })
export class UsersPage extends LithiumElement {
  @inject(UserService) private userService!: UserService;

  async connectedCallback() {
    super.connectedCallback();
    const users = await this.userService.getUsers();
    this.publish("users:list", users);
  }
}
```

---

### HTTP Methods

```typescript
class MyService extends LithiumService {
  // GET
  getItems() {
    return this.get<Item[]>("/items");
  }

  // POST
  createItem(data: Item) {
    return this.post<Item>("/items", data);
  }

  // PUT
  updateItem(id: number, data: Item) {
    return this.put<Item>(`/items/${id}`, data);
  }

  // PATCH
  patchItem(id: number, data: Partial<Item>) {
    return this.patch<Item>(`/items/${id}`, data);
  }

  // DELETE
  deleteItem(id: number) {
    return this.delete(`/items/${id}`);
  }

  // Custom fetch with options
  customRequest() {
    return this.fetch<Data>("/endpoint", {
      method: "POST",
      body: JSON.stringify(data),
      onSuccess: "data:loaded",
      onError: "data:error",
      transform: async (res) => res.text(), // Custom response transform
      skipGlobalInterceptors: true,
    });
  }
}
```

---

### Interceptors

Interceptors modify requests/responses globally or per-service.

#### Global Interceptors

Applied to ALL services.

```typescript
import { LithiumService } from "@lithium-ts/core";

// Auth interceptor
LithiumService.addInterceptor({
  name: "auth",
  request: (ctx) => {
    const token = localStorage.getItem("token");
    if (token) {
      ctx.options.headers = {
        ...ctx.options.headers,
        Authorization: `Bearer ${token}`,
      };
    }
    return ctx;
  },
  error: (error, ctx) => {
    if (error.status === 401) {
      EventBus.emit("auth:expired");
      LithiumRouter.navigate("/login");
    }
    return error;
  },
});

// Logging interceptor
LithiumService.addInterceptor({
  name: "logging",
  request: (ctx) => {
    console.log(`[API] ${ctx.method} ${ctx.url}`);
    return ctx;
  },
  response: (res, data, ctx) => {
    console.log(`[API] Response:`, data);
    return data;
  },
});
```

#### Service-Specific Interceptors

Applied only to one service.

```typescript
@service({
  baseUrl: "https://api.example.com",
  interceptors: [
    {
      name: "retry",
      error: async (error, ctx) => {
        if (error.status === 503) {
          // Retry logic
          await delay(1000);
          return fetch(ctx.url, ctx.options);
        }
        return error;
      },
    },
  ],
})
export class MyService extends LithiumService {}
```

#### Interceptor Context

```typescript
interface InterceptorContext {
  url: string; // Full URL
  options: RequestInit; // Fetch options
  endpoint: string; // Just the endpoint path
  method: string; // HTTP method
}
```

---

## WebSocket Services

For real-time features like chat, notifications, etc.

```typescript
import { LithiumSocketService, socketService, inject } from "@lithium-ts/core";

@socketService()
export class ChatService extends LithiumSocketService {
  constructor() {
    super({
      url: "wss://api.example.com/chat",
      reconnect: true,
      reconnectInterval: 3000,
      maxReconnectAttempts: 5,
      autoConnect: false,
      protocols: ["v1.json"],
      auth: () => localStorage.getItem("token"),
      heartbeatInterval: 30000,
      heartbeatMessage: "ping",
    });

    // Register message handlers
    this.on("message:new", (data) => {
      this.publishToChannel("chat:messages", data);
    });

    this.on("user:typing", (data) => {
      this.emit("chat:typing", data); // Instant event
    });
  }

  // Public methods
  sendMessage(content: string) {
    this.send("message:send", { content });
  }

  joinRoom(roomId: string) {
    this.send("room:join", { roomId });
  }

  // Lifecycle hooks (override as needed)
  protected onConnected() {
    console.log("Connected to chat");
    this.emit("chat:connected", {});
  }

  protected onDisconnected() {
    console.log("Disconnected from chat");
    this.emit("chat:disconnected", {});
  }

  protected onReconnectFailed() {
    this.emit("chat:error", { message: "Connection failed" });
  }
}
```

### Using in Components

```typescript
@definePage({ title: "Chat" })
export class ChatPage extends LithiumElement {
  @inject(ChatService) private chat!: ChatService;

  private messages = this.channel<Message[]>("chat:messages", {
    initialValue: [],
    storage: "page",
  });

  connectedCallback() {
    super.connectedCallback();
    this.chat.connect();

    this.on("chat:typing", (data) => {
      this.showTypingIndicator(data.username);
    });
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.chat.disconnect();
  }

  render() {
    return html`
      <div class="messages">
        ${this.messages
          .get()
          .map((msg) => html` <div class="message">${msg.content}</div> `)}
      </div>
      <input @keyup=${this.handleSend} />
    `;
  }
}
```

### SocketServiceConfig

| Option                 | Type               | Default  | Description                  |
| ---------------------- | ------------------ | -------- | ---------------------------- |
| `url`                  | `string`           | required | WebSocket server URL         |
| `reconnect`            | `boolean`          | `true`   | Auto-reconnect on disconnect |
| `reconnectInterval`    | `number`           | `3000`   | Base reconnect delay (ms)    |
| `maxReconnectAttempts` | `number`           | `5`      | Max attempts (0 = infinite)  |
| `autoConnect`          | `boolean`          | `false`  | Connect on instantiation     |
| `protocols`            | `string[]`         | `[]`     | WebSocket sub-protocols      |
| `auth`                 | `() => string`     | -        | Auth token provider          |
| `heartbeatInterval`    | `number`           | `30000`  | Heartbeat interval (ms)      |
| `heartbeatMessage`     | `string \| object` | `'ping'` | Heartbeat payload            |

---

## API Reference

### Exports

```typescript
// Core
export { LithiumElement, defineElement, StorageType } from "@lithium-ts/core";
export { definePage } from "@lithium-ts/core";
export { LithiumModule, defineModule } from "@lithium-ts/core";
export { LithiumApp, defineApp } from "@lithium-ts/core";
export { html, unsafeCSS, property } from "@lithium-ts/core";

// EventBus
export {
  EventBus,
  ChannelConfig,
  ListenerOptions,
  ChannelInfo,
  EventBusMiddleware,
} from "@lithium-ts/core";

// Router
export {
  LithiumRouter,
  NAVIGATE_EVENT,
  RouteGuardResult,
  RouteLocation,
  NavigateEventData,
  NavigateOptions,
  BeforeRouteCallback,
  AfterRouteCallback,
} from "@lithium-ts/core";

// Decorators
export { defer, DeferOptions } from "@lithium-ts/core";
export { lazy, LazyOptions } from "@lithium-ts/core";
export { conditional } from "@lithium-ts/core";

// Services
export {
  LithiumService,
  ServiceContainer,
  service,
  inject,
  ServiceRequestOptions,
  ServiceConfig,
  ServiceInterceptor,
  InterceptorContext,
} from "@lithium-ts/core";

// WebSocket Services
export {
  LithiumSocketService,
  socketService,
  SocketServiceConfig,
  SocketState,
  SocketMessage,
} from "@lithium-ts/core";
```

---

## License

MIT © 2026