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

@portento/core

v0.2.0

Published

Lightweight dependency injection framework for React with MobX integration

Readme

Portento Core

npm version License: MIT TypeScript

Lightweight dependency injection framework for React and React Native with seamless MobX integration. Build scalable applications with clean architecture using decorators and IoC patterns.

Features

  • 🎯 Three-tier DI scoping - Root, Router, and Component-level dependency management
  • MobX Integration - Automatic observer wrapping for reactive components
  • 🎨 Decorator-based API - Clean, declarative syntax with @Injectable, @Component, @Router
  • 🔄 Automatic Resolution - Smart dependency injection with hierarchical fallback
  • 🧪 Testing Utilities - Reset scopes for isolated unit tests
  • 📦 TypeScript-first - Full type safety and IntelliSense support
  • 🌐 Framework Agnostic - Works with React, React Native, and Angular

Installation

yarn add @portento/core react mobx mobx-react tsyringe reflect-metadata

Or with npm:

npm install @portento/core react mobx mobx-react tsyringe reflect-metadata

TypeScript Configuration

Enable decorators in your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strict": true
  }
}

Import reflect-metadata at your app entry point:

import 'reflect-metadata';
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('MyApp', () => App);

Quick Start

1. Create a Service with @Injectable

import { Injectable } from '@portento/core';
import { makeAutoObservable } from 'mobx';

@Injectable({ providedIn: 'root' })
class AuthService {
  public isAuthenticated = false;
  
  constructor() {
    makeAutoObservable(this);
  }
  
  login(username: string) {
    this.isAuthenticated = true;
  }
  
  logout() {
    this.isAuthenticated = false;
  }
}

2. Create a Component with @Component

import React from 'react';
import { View, Text, Button } from 'react-native';
import { Component, IoComponent } from '@portento/core';

@Component({
  selector: 'home-screen',
  providers: []
})
class HomeScreen implements IoComponent {
  constructor(
    private authService: AuthService
  ) {}
  
  render() {
    return (
      <View>
        <Text>
          {this.authService.isAuthenticated ? 'Logged In' : 'Not Logged In'}
        </Text>
        <Button 
          title="Login" 
          onPress={() => this.authService.login('user')} 
        />
      </View>
    );
  }
}

export const HomeScreenComponent = Component.$provider(HomeScreen);

3. Use in Your App

import { HomeScreenComponent } from './HomeScreen';

export default function App() {
  return <HomeScreenComponent />;
}

Dependency Injection Scoping

Portento Core provides a three-tier scoping hierarchy for dependency management:

1. Root Scope (Singleton)

Services registered at root scope are singletons shared across the entire application:

@Injectable({ providedIn: 'root' })
class UserStore {
  public username = 'Guest';
  
  constructor() {
    makeAutoObservable(this);
  }
}

2. Router Scope (Feature-level)

Services shared across components within the same router:

@Injectable()
class SharedNavStore {
  public currentTab = 0;
  
  constructor() {
    makeAutoObservable(this);
  }
}

@Router({
  selector: 'main-nav-router',
  components: [HomeScreen, ProfileScreen],
  providers: [SharedNavStore]
})
class MainNavRouter implements IoComponent {
  render() {
    return (
      <View>
        <HomeScreen.$provider />
        <ProfileScreen.$provider />
      </View>
    );
  }
}

3. Component Scope (Local)

Services isolated to a single component instance:

@Injectable()
class FormValidator {
  public errors: string[] = [];
  
  validate(value: string) {
    // Validation logic
  }
}

@Component({
  selector: 'login-form',
  providers: [FormValidator]
})
class LoginForm implements IoComponent {
  constructor(private validator: FormValidator) {}
  
  render() {
    // Component implementation
  }
}

Resolution Hierarchy

Dependencies are resolved with automatic fallback:

Component Scope → Router Scope → Root Scope

If a dependency isn't found in the component's providers, it searches the router's providers, then falls back to root scope.

MobX Integration

All components are automatically wrapped with MobX's observer() HOC for reactive updates:

@Injectable({ providedIn: 'root' })
class CounterStore {
  public count = 0;
  
  constructor() {
    makeAutoObservable(this);
  }
  
  increment() {
    this.count++; // Component automatically re-renders
  }
}

@Component({
  selector: 'counter',
  providers: []
})
class Counter implements IoComponent {
  constructor(private store: CounterStore) {}
  
  render() {
    return (
      <View>
        <Text>{this.store.count}</Text>
        <Button title="+" onPress={() => this.store.increment()} />
      </View>
    );
  }
}

Component Export Patterns

Pattern 1: Direct Decoration

@Component({
  selector: 'home-screen',
  providers: []
})
class HomeScreen implements IoComponent {
  render() {
    return <View>...</View>;
  }
}

// Usage in JSX:
<HomeScreen.$provider />

Pattern 2: Separate Class & Export (Recommended)

@Component({
  selector: 'settings-screen',
  providers: []
})
class Settings implements IoComponent {
  render() {
    return <View>...</View>;
  }
}

export const SettingsScreen = Component.$provider(Settings);

// Usage in JSX (cleaner):
<SettingsScreen />

Lifecycle Methods

Components support standard React lifecycle methods:

@Component({
  selector: 'my-component',
  providers: []
})
class MyComponent implements IoComponent {
  componentDidMount() {
    console.log('Component mounted');
  }
  
  componentDidUpdate(prevProps, prevState) {
    console.log('Component updated');
  }
  
  componentWillUnmount() {
    console.log('Component unmounting');
  }
  
  render() {
    return <View>...</View>;
  }
}

Testing Utilities

Reset dependency instances for isolated unit tests:

import { resetScope, resetClass, resetAll } from '@portento/core';

// Reset all root scope instances
resetScope('root');

// Reset specific router scope
resetScope('router', 'main-nav-router');

// Reset component scope
resetScope('component', 'home-screen');

// Reset specific class (conceptually)
resetClass('AuthService');

// Reset everything
resetAll();

API Reference

@Injectable(params)

Register a class as an injectable service.

Parameters:

  • providedIn?: 'root' - Register as root-scoped singleton
@Injectable({ providedIn: 'root' })
class MyService {}

@Component(params)

Create a React component with dependency injection.

Parameters:

  • selector: string - Unique component identifier
  • providers?: Array<Class> - Component-scoped services
@Component({
  selector: 'my-component',
  providers: [LocalService]
})
class MyComponent implements IoComponent {}

@Router(params)

Create a router component that groups child components with shared dependencies.

Parameters:

  • selector: string - Unique router identifier
  • components: Array<Class> - Child components
  • providers?: Array<Class> - Router-scoped services
@Router({
  selector: 'main-router',
  components: [ScreenA, ScreenB],
  providers: [SharedStore]
})
class MainRouter implements IoComponent {}

IoComponent Interface

Base interface for all components:

interface IoComponent<Props = any, State = any> {
  state?: State;
  componentDidMount?(): void;
  componentDidUpdate?(prevProps: Props, prevState: State): void;
  componentWillUnmount?(): void;
  render(): React.ReactNode;
}

Controller Type

Access React component state and methods:

interface Controller<Props = any, State = any> {
  props: Props;
  state: State;
  setState: (state: Partial<State> | ((prevState: State) => State)) => void;
  forceUpdate: () => void;
}

Inject the controller:

@Component({
  selector: 'stateful-component',
  providers: []
})
class StatefulComponent implements IoComponent {
  constructor(private controller: Controller) {}
  
  updateState() {
    this.controller.setState({ counter: 1 });
  }
}

Examples

Complete usage examples are available in the @portento/core-examples package:

yarn add @portento/core-examples

Examples include:

  • StoreExample - MobX observable stores with different scopes
  • ScopingExample - Dependency injection hierarchy demonstration
  • RouterScopeExample - Shared state across router components
  • ResetExample - Cleanup utilities for testing

Troubleshooting

Decorators not working

Ensure experimentalDecorators and emitDecoratorMetadata are enabled in tsconfig.json.

"Design:paramtypes" metadata missing

Import reflect-metadata at your app entry point before any other imports.

MobX observables not triggering re-renders

Make sure your stores use makeAutoObservable(this) in the constructor.

Dependency not found

Check the resolution order: component providers → router providers → root scope. Ensure the service is registered at the appropriate level.

Architecture Documentation

For detailed architecture information, see:

License

MIT © Luca Leone

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Repository

https://github.com/luca-leone/portento-core