Skip to content

Commit 991cf3e

Browse files
committed
Added wrapper for transversals caller to use outside of our assembler
1 parent 86ff435 commit 991cf3e

File tree

67 files changed

+2588
-590
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2588
-590
lines changed

docs/assemblerjs/api/transversal-weaver.md

Lines changed: 446 additions & 0 deletions
Large diffs are not rendered by default.

docs/assemblerjs/api/types.md

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -302,21 +302,36 @@ Information about the point of execution where an advice is applied.
302302

303303
```typescript
304304
interface JoinPoint {
305-
target: any; // The target instance
306-
methodName: string; // The name of the method being called
307-
args: any[]; // The arguments passed to the method
308-
result?: any; // The result of the method (for after advice)
309-
error?: any; // The error thrown by the method (if any)
305+
target: any; // The target instance
306+
methodName: string; // The name of the method being called
307+
args: any[]; // The arguments passed to the method
308+
result?: any; // The result of the method (for after advice)
309+
error?: any; // The error thrown by the method (if any)
310+
caller?: string; // Class name of the caller (if tracked)
311+
callerIdentifier?: string | symbol; // Optional unique identifier for the caller
310312
}
311313
```
312314

315+
**Caller Properties:**
316+
317+
- `caller` - The name of the class or component that initiated the method call. Populated automatically for DI-managed calls, or set via `TransversalWeaver.withCaller()` for external callers.
318+
- `callerIdentifier` - Optional identifier (typically a Symbol or UUID) to uniquely identify the caller. Useful for request tracing and correlation IDs.
319+
313320
**Usage:**
314321

315322
```typescript
316323
function logJoinPoint(joinPoint: JoinPoint) {
317324
console.log('Method:', joinPoint.methodName);
318325
console.log('Args:', joinPoint.args);
319326
console.log('Result:', joinPoint.result);
327+
328+
// Caller tracking
329+
if (joinPoint.caller) {
330+
console.log('Called by:', joinPoint.caller);
331+
if (joinPoint.callerIdentifier) {
332+
console.log('Caller ID:', joinPoint.callerIdentifier);
333+
}
334+
}
320335
}
321336
```
322337

@@ -328,6 +343,8 @@ Extended join point with control flow for advices.
328343
interface AdviceContext extends JoinPoint {
329344
proceed?(): any | Promise<any>; // Continue to next advice or original method
330345
config?: Record<string, any>; // Optional config from @Affect decorator
346+
caller?: string; // Class name of the caller (inherited from JoinPoint)
347+
callerIdentifier?: string | symbol; // Caller ID (inherited from JoinPoint)
331348
}
332349
```
333350

@@ -339,13 +356,24 @@ class MyTransversal {
339356
@Around('execution(*.*)')
340357
async intercept(context: AdviceContext) {
341358
console.log('Before:', context.methodName);
359+
if (context.caller) {
360+
console.log('Caller:', context.caller);
361+
}
342362

343363
// Call next advice or original method
344364
const result = await context.proceed!();
345365

346366
console.log('After:', result);
347367
return result;
348368
}
369+
370+
@Before('execution(*.delete)')
371+
checkAuthority(context: AdviceContext) {
372+
// Use caller information for authorization
373+
if (context.caller && !this.canDelete(context.caller)) {
374+
throw new Error(`${context.caller} not authorized to delete`);
375+
}
376+
}
349377
}
350378
```
351379

docs/assemblerjs/core-concepts/transversals-aop.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,184 @@ class LifecycleTransversal implements AbstractTransversal {
376376
}
377377
```
378378

379+
## Caller Tracking
380+
381+
Transversals support caller tracking, allowing you to identify which assemblage or external component initiated a method call. This is useful for audit logging, permission checking, and request tracing.
382+
383+
### Using Caller Information in Advices
384+
385+
The `AdviceContext` provides caller information:
386+
387+
- `caller` - The class name of the caller
388+
- `callerIdentifier` - Optional unique identifier (string or symbol) for the caller
389+
390+
```typescript
391+
@Transversal()
392+
class AuditTransversal implements AbstractTransversal {
393+
@Before('execution(*.*)')
394+
auditCall(context: AdviceContext) {
395+
console.log(
396+
`[AUDIT] ${context.caller || 'Unknown'} called ${context.target.constructor.name}.${context.methodName}`
397+
);
398+
}
399+
400+
@Before('execution(*.delete)')
401+
checkDeletePermission(context: AdviceContext) {
402+
// Only allow deletions from specific callers
403+
if (context.caller !== 'AdminService') {
404+
throw new Error(`Access denied: ${context.caller} cannot delete`);
405+
}
406+
}
407+
}
408+
```
409+
410+
### Tracking External Callers
411+
412+
For callers outside the DI container (e.g., Vue components, external scripts), use `TransversalWeaver.withCaller()` or `TransversalWeaver.wrapCaller()`:
413+
414+
#### One-time execution with withCaller
415+
416+
```typescript
417+
import { TransversalWeaver } from 'assemblerjs';
418+
419+
// In a Vue component
420+
export default {
421+
methods: {
422+
async saveUser() {
423+
await TransversalWeaver.withCaller('UserEditComponent', async () => {
424+
await this.userService.save(userData);
425+
// Advices will see caller = 'UserEditComponent'
426+
});
427+
}
428+
}
429+
};
430+
```
431+
432+
#### Reusable wrapped functions with wrapCaller
433+
434+
For functions you'll call multiple times, use `wrapCaller` to create a wrapped function:
435+
436+
```typescript
437+
import { TransversalWeaver } from 'assemblerjs';
438+
439+
// In a Vue component
440+
export default {
441+
setup() {
442+
// Create wrapped function once
443+
const mergeClasses = TransversalWeaver.wrapCaller(
444+
'LeafletMap',
445+
'LeafletMap.vue',
446+
(...args) => tailwind.mergeClasses(...args)
447+
);
448+
449+
return {
450+
// Can be called multiple times, always maintains caller context
451+
mergeClasses
452+
};
453+
}
454+
};
455+
```
456+
457+
### Setting Caller with Identifier
458+
459+
For more detailed tracking, provide an identifier alongside the caller:
460+
461+
```typescript
462+
@Transversal()
463+
class RequestTracingTransversal implements AbstractTransversal {
464+
@Before('execution(*.*)')
465+
traceRequest(context: AdviceContext) {
466+
const callerId = context.callerIdentifier
467+
? ` (ID: ${String(context.callerIdentifier)})`
468+
: '';
469+
console.log(`Request from: ${context.caller}${callerId}`);
470+
}
471+
}
472+
473+
// Usage
474+
const requestId = Symbol('request-123');
475+
await TransversalWeaver.withCaller('APIController', requestId, async () => {
476+
await service.processRequest();
477+
});
478+
```
479+
480+
### Getting Current Caller Context
481+
482+
Access the current caller outside of advices using `TransversalWeaver.getCurrentCaller()`:
483+
484+
```typescript
485+
class ServiceA {
486+
someMethod() {
487+
const caller = TransversalWeaver.getCurrentCaller();
488+
if (caller) {
489+
console.log(`Called by: ${caller.className} (ID: ${caller.identifier})`);
490+
}
491+
}
492+
}
493+
```
494+
495+
### Working Without Transversals
496+
497+
Caller tracking works even when no transversals are engaged:
498+
499+
```typescript
500+
// No transversals needed for caller context to work
501+
@Assemblage()
502+
class App {
503+
constructor(private service: ServiceA) {}
504+
505+
async run() {
506+
await TransversalWeaver.withCaller('App', async () => {
507+
const result = await this.service.someMethod();
508+
// Even without advices, getCurrentCaller() will return 'App'
509+
});
510+
}
511+
}
512+
```
513+
514+
### Use Cases
515+
516+
1. **Audit Logging** - Track who accessed sensitive data
517+
```typescript
518+
@Before('execution(*.findSensitiveData)')
519+
auditAccess(context: AdviceContext) {
520+
this.logger.info(`${context.caller} accessed sensitive data`);
521+
}
522+
```
523+
524+
2. **Permission Checking** - Restrict operations by caller
525+
```typescript
526+
@Before('execution(*.delete)', 100)
527+
checkAuthorization(context: AdviceContext) {
528+
if (!this.canDelete(context.caller)) {
529+
throw new Error(`${context.caller} not authorized to delete`);
530+
}
531+
}
532+
```
533+
534+
3. **Request Tracing** - Track request flow across services
535+
```typescript
536+
@Before('execution(*.*)')
537+
traceFlow(context: AdviceContext) {
538+
if (context.callerIdentifier) {
539+
console.log(`[Trace ${context.callerIdentifier}] ${context.caller} → ${context.methodName}`);
540+
}
541+
}
542+
```
543+
544+
4. **Conditional Behavior** - Different behavior based on caller
545+
```typescript
546+
@Around('execution(*.getData)')
547+
async getData(context: AdviceContext) {
548+
if (context.caller === 'AdminService') {
549+
return await context.proceed!(); // Full data
550+
} else {
551+
const data = await context.proceed!();
552+
return this.filterSensitiveFields(data); // Filtered data
553+
}
554+
}
555+
```
556+
379557
## Performance Considerations
380558

381559
- Transversals add overhead to method calls

docs/assemblerjs/decorators/aop-decorators.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ methodName(context: AdviceContext): void | Promise<void>
113113
- `methodName` - Name of the intercepted method
114114
- `args` - Array of arguments passed to the method
115115
- `config` - Optional configuration from @Affect decorator
116+
- `caller` (optional) - Class name of the component that initiated the call
117+
- `callerIdentifier` (optional) - Unique identifier for the caller (useful for tracing)
118+
119+
See [Caller Tracking](../core-concepts/transversals-aop.md#caller-tracking) for detailed information.
116120

117121
### Examples
118122

@@ -128,15 +132,29 @@ class LoggingTransversal {
128132
}
129133
```
130134

135+
#### With Caller Tracking
136+
137+
```typescript
138+
@Transversal()
139+
class AuditTransversal {
140+
@Before('execution(*.*)')
141+
auditCall(context: AdviceContext) {
142+
const caller = context.caller || 'Unknown';
143+
console.log(`[AUDIT] ${caller} called ${context.methodName}`);
144+
}
145+
}
146+
```
147+
131148
#### With Priority
132149

133150
```typescript
134151
@Transversal()
135152
class SecurityTransversal {
136153
@Before('execution(*.delete*)', 100) // High priority
137154
checkPermission(context: AdviceContext) {
138-
if (!this.hasPermission()) {
139-
throw new Error('Access denied');
155+
// Only AdminService can delete
156+
if (context.caller !== 'AdminService') {
157+
throw new Error(`Access denied: ${context.caller}`);
140158
}
141159
}
142160
}
@@ -204,6 +222,8 @@ methodName(context: AdviceContext): void | Promise<void>
204222
### AdviceContext Properties (in addition to @Before)
205223

206224
- `result` - The return value of the intercepted method
225+
- `caller` (optional) - Class name of the component that initiated the call
226+
- `callerIdentifier` (optional) - Unique identifier for the caller
207227

208228
### Examples
209229

@@ -219,6 +239,19 @@ class LoggingTransversal {
219239
}
220240
```
221241

242+
#### With Caller Information
243+
244+
```typescript
245+
@Transversal()
246+
class AuditTransversal {
247+
@After('execution(*.delete)')
248+
auditDeletion(context: AdviceContext) {
249+
const caller = context.caller || 'Unknown';
250+
console.log(`[AUDIT] ${caller} deleted ${context.target.constructor.name}`);
251+
}
252+
}
253+
```
254+
222255
#### Processing Results
223256

224257
```typescript
@@ -283,6 +316,8 @@ methodName(context: AdviceContext): any | Promise<any>
283316
### AdviceContext Properties (in addition to @Before/@After)
284317

285318
- `proceed()` - Function to invoke the next advice or original method
319+
- `caller` (optional) - Class name of the component that initiated the call
320+
- `callerIdentifier` (optional) - Unique identifier for the caller
286321

287322
### Important
288323

@@ -309,6 +344,27 @@ class PerformanceTransversal {
309344
}
310345
```
311346

347+
#### With Caller Tracking
348+
349+
```typescript
350+
@Transversal()
351+
class DataFilteringTransversal {
352+
@Around('execution(*.getData)')
353+
async filterByRole(context: AdviceContext) {
354+
const data = await context.proceed!();
355+
356+
// Different filtering based on caller
357+
if (context.caller === 'AdminDashboard') {
358+
return data; // No filtering
359+
} else if (context.caller === 'PublicAPI') {
360+
return this.filterPublic(data);
361+
} else {
362+
return [];
363+
}
364+
}
365+
}
366+
```
367+
312368
#### Error Handling
313369

314370
```typescript

0 commit comments

Comments
 (0)