Options
All
  • Public
  • Public/Protected
  • All
Menu

Context objects are used to essentially track the execution flow of some processing functions, essentially to:

  • enable meaningful reporting of what is going on (wrong or not) during module's execution (see also Journal)
  • understand the bottleneck in terms of performances
  • enable Log broadcasting to multiple destinations

Context can be used in synchronous or asynchronous scenarios, the following example illustrates both usages (code is simplified to only keep the relevant points):

 class Module extends ModuleFlux{

     output$ : Pipe<unknown>

     constructor(params) {
         super(params)
             addInput({  
                 onTriggered: ({data, context}) => 
                     this.process(data, context)
             })
             this.addOutput({id:'output'})
     }
       
     process(data, context: Context) {

         let data1 = context.withChild( 'syncStep', () => syncStep(data, context) )
         ctx.info("Synchronous step was successful", data1)
         
         asyncStep(data1, context).subscribe( 
             (result) => {
                 this.output$.next(result);
                 // it is always the responsibility of the developer to close
                 // the context provided to 'onTriggered' 
                 context.end()
              };
             (error) => {    
                 ctx.error( error )
                 context.end()
             }elapsed time
     }

     syncStep( data, context: Context){

         context.info("Starting sync step", data)   
         let value // = ... the result of some computations
         return value
     }

     async asyncStep( data: unknown, context: Context): Observable<any> {

         let ctx = context.startChild('async step')
         ctx.info("Starting async step, expect 10 values to come", data)   

         return from(
             // a source of async data, e.g. requests, multi-threaded computations, etc
             ).pipe(
             tap( (value) => ctx.info("got a value", value))
             take(10),
             reduce( (acc,e) => acc.concat(e), []) 
             tap( (acc) => { 
                 ctx.info("got all 10 values", acc)
                 ctx.end()
             })
         )
     }
}

The difference between sync. and async. scenario is whether or not it is needed to call context.close:

  • for synchronous cases, the Context class provide the method withChild, the developer is not in charge to start or end the child context as it is automatically managed. If an error is thrown and not catched during the callback execution, the child context end (along with its parents), the error is reported in the child context, and finally the error is re-thrown.
  • for asynchronous cases, the Context class provides the method startChild, it is then the developer responsibility to call the method end at the right time, and to deal with eventual exceptions.

The natural representation of a context, as presented in Flux applications, is a tree view representing the tree structure of the chain of function calls. The children of a context can be of two types:

Logs broadcasting

Context objects can feature broadcasting channels$ to multiple destinations and with different filters through LogChannel. For instance, [[ModuleFlux]] use two channels:

  • all logs are broadcasted to <a href="core_concepts.moduleflux.html#logs_">ModuleFlux.logs$</a>
    
  • error logs are broadcasted to <a href="lib_environment.environment.html#errors_">Environment.errors$</a>
    

A note about user-context

The Context object also conveys the user-context. The user-context is a key->value dictionary filled by the builder of a Flux application (using adaptors) that needs to be forwarded from modules to modules. The library can not automatically forward the user-context when output are sent: it is not always as meaningful as expected (e.g. what is the output context of a combining type of module?), and the asynchronous (possibly multi-threaded) nature of module's processes makes it impossible (meaning we did not find a safe way of doing so 🤫).

This explains why, when emitting a value through an output pipe, you should explicitly pass back the context that has been provided for you at the first place (to the onTriggered callback).

Hierarchy

  • Context

Index

Constructors

constructor

  • new Context(title: string, userContext: {}, channels$?: LogChannel<unknown>[], parent?: any): Context
  • Parameters

    • title: string

      title of the context

    • userContext: {}

      user-context

      • [key: string]: unknown
    • channels$: LogChannel<unknown>[] = ...

      broadcasting channels

    • parent: any = ...

      parent context if not root

    Returns Context

Properties

Readonly channels$

channels$: LogChannel<unknown>[] = ...

children

children: (Context | Log)[] = ...

Context's children

Private endTimestamp

endTimestamp: number

id

id: string = ...

Readonly parent

parent: any = ...

Readonly startTimestamp

startTimestamp: number = ...

timestamp corresponding to the instance creation

Readonly title

title: string

userContext

userContext: {}

Type declaration

  • [key: string]: unknown

Methods

Private addLog

  • addLog(log: Log): void

elapsed

  • elapsed(from?: number): number
  • Parameters

    • Optional from: number

      a reference timestamp, use this.startTimestamp if not provided

    Returns number

    Either the 'true' elapsed time of this context if it has ended or the maximum elapsed(this.startTimestamp) of the children (recursive lookup)

end

  • end(): void

error

  • error(error: Error, data?: unknown): void

info

  • info(text: string, data?: unknown): void

root

startChild

  • startChild(title: string, withUserContext?: {}): Context
  • Start a new child context, supposed to be used in asynchronous scenarios.

    The attribute userContext of the child context is a clone of the parent's user-context, eventually merged with provided withUserContext. withUserContext is needed for very specific cases, usually there is no need to provide one.

    Parameters

    • title: string

      title of the child context

    • withUserContext: {} = ...

      user context entries to add

      • [key: string]: unknown

    Returns Context

    the child context

status

warning

  • warning(text: string, data?: unknown): void

withChild

  • withChild<T>(title: string, callback: (context: Context) => T, withUserInfo?: {}): T
  • Wrap a new child context around a callback, supposed to be used in synchronous scenarios. In this case the developer does not need to handle start or end of the child context.

    The attribute userContext of the child context is a clone of the parent's user-context, eventually merged with provided withUserContext. withUserContext is needed for very specific cases, usually there is no need to provide one.

    If an error is thrown and not catched during the callback execution, the child context end (along with its parents), the error is reported in the child context, and finally the error is re-thrown.

    Type parameters

    • T

    Parameters

    • title: string

      title of the child context

    • callback: (context: Context) => T

      the callback, the child context is provided as argument of the callback

    • withUserInfo: {} = ...
      • [key: string]: unknown

    Returns T

    the child context

Generated using TypeDoc