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

@lunafw/core

v1.2.2

Published

Core DI container, module system and lifecycle hooks for the Luna framework

Readme

Installation

npm install @lunafw/core

Add to your tsconfig.json:

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

Module system

Modules are the building blocks of a Luna application. Each module groups related providers and controls what is visible to the rest of the application via exports.

AppModule
  ├── imports: [UsersModule, DatabaseModule]
  │
  ├── UsersModule
  │     providers: [UsersService, UsersController]
  │     exports:   [UsersService]   ← visible to importers
  │
  └── DatabaseModule
        providers: [DbConnection]
        exports:   [DbConnection]

Declaring a module

import { Module } from '@lunafw/core'

@Module({
  imports:   [DatabaseModule],   // modules whose exports become available here
  providers: [UsersService, UsersController],
  exports:   [UsersService],     // make UsersService available to importers
})
export class UsersModule {}

Root module

The root module is the entry point passed to LunaFactory. It imports every feature module that the application needs:

import { Module } from '@lunafw/core'
import { UsersModule } from './users/users.module'
import { PostsModule } from './posts/posts.module'

@Module({
  imports: [UsersModule, PostsModule],
})
export class AppModule {}

Dependency Injection

@Injectable

Mark any class as an injectable provider. Luna resolves its constructor dependencies automatically using TypeScript metadata:

import { Injectable } from '@lunafw/core'

@Injectable()
export class UsersService {
  constructor(private readonly db: DatabaseService) {}
  // DatabaseService is resolved from the same module's container
}

Provider scopes

import { Injectable, ProviderScope } from '@lunafw/core'

@Injectable(ProviderScope.Singleton)   // default — one instance per module
@Injectable(ProviderScope.Transient)   // new instance every time it is requested

@Inject

Use @Inject when a token is a string or symbol — TypeScript cannot infer these automatically:

import { Inject, Injectable } from '@lunafw/core'

@Injectable()
export class AppService {
  constructor(
    private readonly userService: UserService,         // resolved by type
    @Inject('API_URL') private readonly apiUrl: string, // resolved by string token
    @Inject(DB_TOKEN) private readonly db: DbClient,   // resolved by symbol token
  ) {}
}

Custom providers

Three factory styles are available for cases where simple class injection is not enough:

@Module({
  providers: [
    // Value provider — inject a literal value or pre-built object
    { provide: 'API_URL', useValue: process.env.API_URL ?? 'http://localhost' },

    // Class provider — swap implementations without changing consumers
    { provide: LoggerService, useClass: ProductionLoggerService },

    // Factory provider — build the instance with injected dependencies
    {
      provide: 'DB_CONNECTION',
      inject: [ConfigService],
      useFactory: (config: ConfigService) => createConnection(config.get('DATABASE_URL')),
    },
  ],
})
export class AppModule {}

defineProvider helper

defineProvider gives you auto-complete and type safety when defining factory providers:

import { defineProvider } from '@lunafw/core'

export const cacheProvider = defineProvider({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => new CacheClient(config.get('REDIS_URL', 'redis://localhost')),
})

@Module({ providers: [ConfigService, cacheProvider] })
export class CacheModule {}

Conditional providers

Register a provider only when a runtime condition is met:

defineProvider({
  useFactory: () => new DevLogger(),
  when: () => process.env.NODE_ENV === 'development',
})

Lazy providers

Defer instantiation to the first time the token is resolved (useful for expensive connections):

defineProvider({
  inject: [ConfigService],
  useFactory: (config) => new SearchClient(config.get('ELASTIC_URL')),
  lazy: true,
})

Error handling

DependencyResolutionError is thrown when a provider cannot be resolved. Common causes: missing registration, wrong token, or a circular dependency.

import { DependencyResolutionError } from '@lunafw/core'

try {
  const service = app.get(UnregisteredService)
} catch (e) {
  if (e instanceof DependencyResolutionError) {
    console.error('[DI]', e.message)
    // e.g. "[Luna] Cannot resolve UserService: DatabaseService is not registered"
  }
}

Lifecycle hooks

Any provider can implement one or more lifecycle interfaces. Luna calls them in the documented order:

| Hook | When it runs | |---|---| | onModuleInit() | After all providers in all modules are instantiated | | onApplicationBootstrap() | After all onModuleInit hooks complete | | onModuleDestroy() | On SIGTERM / SIGINT, before shutdown | | beforeApplicationShutdown() | After all onModuleDestroy hooks | | onApplicationShutdown() | Last hook before the process exits |

import { Injectable } from '@lunafw/core'

@Injectable()
export class DatabaseService {
  async onModuleInit() {
    await this.connect()
    console.log('Database connected')
  }

  async onModuleDestroy() {
    await this.disconnect()
    console.log('Database disconnected')
  }
}

Debugging

app.inspect(token) returns a serialisable snapshot of the provider dependency tree — useful for tracing unexpected resolutions:

import { LunaFactory } from '@lunafw/core'

const app = await LunaFactory.create(AppModule)
console.log(JSON.stringify(app.inspect(UsersController), null, 2))
// {
//   "token": "UsersController",
//   "scope": "singleton",
//   "dependencies": [
//     { "token": "UsersService", "scope": "singleton", "dependencies": [...] }
//   ]
// }

License

MIT