IoC container and dependency injection for the @alt-javascript framework. Provides component definitions, autowiring, lifecycle management, application events, AOP, and conditional beans — all in pure JavaScript ES modules.
The design is a direct port of the Spring Framework's ApplicationContext and component model to idiomatic JavaScript.
Part of the @alt-javascript monorepo.
npm install @alt-javascript/cdiimport { ApplicationContext, Context, Singleton } from '@alt-javascript/cdi';
import { Boot } from '@alt-javascript/boot';
import { EphemeralConfig } from '@alt-javascript/config';
class UserRepository {
constructor() { this.users = []; }
add(user) { this.users.push(user); }
findAll() { return this.users; }
}
class UserService {
constructor() { this.userRepository = null; } // autowired by name
createUser(name) { this.userRepository.add({ name }); }
}
const config = new EphemeralConfig({ logging: { level: { ROOT: 'info' } } });
Boot.boot({ config });
const context = new Context([
new Singleton(UserRepository),
new Singleton(UserService),
]);
const appCtx = new ApplicationContext({ contexts: [context], config });
await appCtx.start();
appCtx.get('userService').createUser('Craig');
console.log(appCtx.get('userRepository').findAll()); // [{ name: 'Craig' }]import { Singleton, Service, Property } from '@alt-javascript/cdi/context/index.js';
// Null-property naming → autowired by name (equivalent to Spring @Autowired)
class OrderService {
constructor() {
this.orderRepository = null; // autowired
this.emailService = null; // autowired
this.logger = null; // autowired
// Property injection — resolved from config (equivalent to Spring @Value)
this.maxRetries = '${order.maxRetries:3}';
this.currency = '${app.currency:USD}';
}
init() { /* called after wiring — @PostConstruct equivalent */ }
destroy() { /* called on shutdown — @PreDestroy equivalent */ }
}const context = new Context([
{
name: 'myService',
Reference: MyService,
scope: 'singleton',
condition: (config, components) => config.has('feature.enabled'),
},
]);The ApplicationContext lifecycle mirrors Spring's refresh() → start() → stop() sequence:
| Phase | Method | Spring equivalent |
|---|---|---|
| Wire + init | appCtx.prepare() |
refresh() |
| Run | appCtx.run() |
start() |
| Both | appCtx.start() |
run() (SpringApplication) |
| Shutdown | appCtx.stop() |
close() |
import {
conditionalOnProperty,
conditionalOnMissingBean,
conditionalOnProfile,
allOf,
} from '@alt-javascript/cdi';
// Register only when config property is set to 'true'
const context = new Context([{
name: 'cacheService',
Reference: RedisCache,
condition: conditionalOnProperty('cache.enabled'),
}]);import { createProxy, matchMethod } from '@alt-javascript/cdi';
const proxy = createProxy(myService, {
before: (ctx) => console.log(`Calling ${ctx.method}`),
after: (ctx) => console.log(`Done ${ctx.method} → ${ctx.result}`),
around: (ctx) => { /* intercept */ return ctx.proceed(); },
throws: (ctx) => console.error(`Error in ${ctx.method}`, ctx.error),
}, matchMethod(/^find/));import { ApplicationEvent, ApplicationEventPublisher } from '@alt-javascript/cdi';
class OrderCreatedEvent extends ApplicationEvent {
constructor(order) { super('order.created'); this.order = order; }
}
class OrderListener {
onApplicationEvent(event) {
if (event.type === 'order.created') {
this.emailService.send(event.order.customer, 'Your order is confirmed');
}
}
}| Spring concept | @alt-javascript/cdi equivalent |
|---|---|
@Component, @Service, @Repository |
Singleton, Service |
@Autowired (field injection) |
Null-property naming convention |
@Value("${key:default}") |
Placeholder strings in constructor |
@PostConstruct |
init() method |
@PreDestroy |
destroy() method |
ApplicationContext.refresh() |
appCtx.prepare() |
ApplicationContext.start() |
appCtx.run() |
ApplicationEvent / ApplicationListener |
ApplicationEvent + event bus |
@Conditional / @ConditionalOnProperty |
conditionalOnProperty() etc. |
BeanPostProcessor |
BeanPostProcessor |
@Aspect (Spring AOP) |
createProxy() |
MIT