Powerful Aspect Oriented Programming (AOP) for JavaScript/TypeScript, inspired by Spring AOP
- βοΈ Clean Separation of Concerns - Isolate cross-cutting concerns from your business logic
- π Method Interception - Before, After, Around, AfterReturning, AfterThrowing advice
- π― Pointcut Expressions - Target specific methods and classes with powerful expressions
- π§© Composable Aspects - Create reusable aspects and combine them effortlessly
- β‘ Performance Optimized - Minimal runtime overhead with smart caching
- π TypeScript First - Full type safety with excellent IDE support
- π Transparent Proxies - Maintains original method signatures and stack traces
npm install @meshcraft/aspect
# OR
yarn add @meshcraft/aspect
# OR
pnpm add @meshcraft/aspectimport { createAspect, Around, Pointcut } from '@meshcraft/aspect';
// Create a simple logging aspect
const LoggingAspect = createAspect({
// Define a pointcut that targets service methods
@Pointcut('execution(*.service.*(..))')
serviceMethod() {},
// Apply "around" advice to all methods matching the pointcut
@Around('serviceMethod()')
async logMethodExecution(joinPoint) {
const { method, args, proceed } = joinPoint;
console.log(`Executing ${method} with arguments:`, args);
const startTime = performance.now();
try {
// Proceed with the original method call
const result = await proceed();
console.log(`${method} returned:`, result);
return result;
} catch (error) {
console.error(`${method} threw an error:`, error);
throw error;
} finally {
const endTime = performance.now();
console.log(`${method} execution time: ${endTime - startTime}ms`);
}
}
});
// Create a class with some business logic
class UserService {
async findById(id: string) {
// Database operations...
return { id, name: 'John Doe' };
}
async createUser(userData: any) {
// User creation logic...
return { id: '123', ...userData };
}
}
// Apply the aspect to the service
const userService = LoggingAspect.apply(new UserService());
// Use the proxied service - aspects will be applied automatically
await userService.findById('123');
// Console:
// Executing findById with arguments: ['123']
// findById returned: { id: '123', name: 'John Doe' }
// findById execution time: 5.23msAspects are the core containers for cross-cutting concerns. They define pointcuts and advice that describe where and how to apply additional behavior.
Joinpoints represent specific points in your application's execution where aspects can be applied. In @Meshcraft/aspect, joinpoints are typically method executions.
Pointcuts are expressions that select specific joinpoints in your code. They determine which methods your aspects will target.
@Pointcut('execution(com.example.service.*.*(..))')
serviceLayer() {}Advice defines the behavior to apply at selected joinpoints. @Meshcraft/aspect offers several types of advice:
- @Before - Executes before the joinpoint
- @After - Executes after the joinpoint (regardless of outcome)
- @AfterReturning - Executes after successful completion
- @AfterThrowing - Executes if the joinpoint throws an exception
- @Around - Wraps the joinpoint with custom behavior
const TransactionAspect = createAspect({
@Pointcut('execution(@Transactional *.*(..))')
transactionalMethod() {},
@Around('transactionalMethod()')
async manageTransaction(joinPoint) {
const tx = await db.beginTransaction();
try {
const result = await joinPoint.proceed();
await tx.commit();
return result;
} catch (error) {
await tx.rollback();
throw error;
}
}
});
// Usage with decorator
class OrderService {
@Transactional()
async createOrder(order) {
// Order creation logic that will be automatically wrapped in a transaction
}
}const CachingAspect = createAspect({
@Pointcut('execution(@Cacheable *.*(..))')
cacheableMethod() {},
@Around('cacheableMethod()')
async cache(joinPoint) {
const { target, method, args } = joinPoint;
const cacheKey = `${target.constructor.name}.${method}(${JSON.stringify(args)})`;
const cachedValue = await cacheManager.get(cacheKey);
if (cachedValue) {
return cachedValue;
}
const result = await joinPoint.proceed();
await cacheManager.set(cacheKey, result);
return result;
}
});@Meshcraft/aspect provides a powerful expression language for defining pointcuts:
| Expression | Description |
|---|---|
execution(* *.*(..)) |
All methods in all classes |
execution(com.example.service.*.*(..)) |
All methods in service classes |
execution(* *.find*(..)) |
All methods starting with "find" |
execution(@Transactional * *.*(..)) |
Methods with @Transactional annotation |
execution(* *.*(String, ..)) |
Methods with String as first parameter |
within(com.example.service.*) |
All joinpoints within service package |
@Meshcraft/aspect is designed with performance in mind:
- Pointcut expressions are parsed and optimized at load time
- Proxies use minimal reflection to maintain performance
- Advanced caching mechanisms reduce runtime overhead
For production environments, consider:
// Enable production mode to apply additional optimizations
import { configure } from '@meshcraft/aspect';
configure({
production: process.env.NODE_ENV === 'production',
cacheSize: 1000
});Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Spring Framework - For the original AOP concepts and implementation
- TypeScript team - For the amazing type system that makes this possible