Skip to content

Callbacks and Overrides

This document provides a high-level overview of the mechanisms used by jsii to allow foreign code to override JavaScript methods. It details the responsibilities of the jsii kernel and of the foreign library, and provides implementation guidelines.

Identifying Overrides

The jsii kernel allows foreign code to register overrides for the following use-cases:

  • Overriding a class' non-static member
  • Implementing an abstract member (including interface members)

Info

It is possible for foreign code to override a class' constructor, but those cannot be registered in the jsii kernel as it cannot trigger instantiation of a foreign class directly. Foreign constructors always delegate to the JavaScript constructor, which is happens via the create operation, during which overrides are registered.

All cases where foreign code should be executed in lieu of JavaScript code must be identified and declared properly, as the jsii kernel otherwise has no way to determine a foreign implementation exists.

Where possible, the jsii runtime library for the foreign language will use reflection APIs to transparently discover which API elements are overridden or implemented. In cases where reflection is expensive, the jsii runtime library will try to cache findings as appropriate in order to avoid duplication of this effort.

In case the foreign language does not have direct support for overriding (e.g: Go), or lacks the necessary reflection tools to allow automatic identification of overrides, the jsii runtime library may expose APIs allowing users to register implementation overrides manually.

Declaring Overrides

The foreign library is responsible for declaring overrides to the jsii kernel. Those are declared for every object instance using the overrides property of the kernel.create request. Each entry in the overrides list declares an overriden property or method.

Each override declaration may optionally include a cookie: this string will not be interpreted by the jsii kernel in any way, and will simply be passed back passed back with any request to invoke the overridden member's foreign implementation.

Info

It is possible to register overrides to members that do not formally exist on a type. Since the jsii kernel has no type information available for those, it will handle them as follows:

  • Method overrides are assumed to have the following signature:
    overridden(...args: any[]): any
    
  • Property overrides are assumed to be any-valued and mutable

Danger

This should generally be avoided as it can result in incoherent data serialization happening when the jsii kernel receives and returns values.

Invoking Overrides

Once object instances have been created in the jsii kernel with overrides, execution of JavaScript code may require executing foreign code. The jsii kernel signals this to the jsii runtime library by responding with a callback request instead of the typical response type for the original request (i.e: InvokeResponse, GetResponse or SetResponse). Several such callbacks may be necessary in order to complete the original request. When the original request is finally able to complete, its response is returned.

The jsii runtime library must respond to each callback request with a complete response, allowing the jsii kernel to resume fulfilling the original request. In order to do this, the jsii runtime library obtains the foreign object corresponding to the callback request's receiver, then invokes the corresponding implementation (for example, using reflection).

When needed, the original JavaScript implementation can be delegated to (many languages refer to this as super(...) or some similar idiom).

Example

Assuming we have the following TypeScript types defined:

export abstract class FooClass {
  protected abstract baz: string;

  public bar(): string {
    return this.reverse() ? Array.from(this.baz).reverse().join('') : this.baz;
  }

  protected reverse(): boolean {
    return false;
  }
}

And we have the following Foreign application (assuming Java):

public final class Main extends FooClass {
  public static final void main(final String[] args) {
    final FooClass self = new Main();
    System.out.println(self.bar());
  }

  @Override
  public String getBaz() {
    return "baz";
  }

  @Override
  public boolean reverse() {
    return true;
  }
}

The schematized exchange between the jsii runtime library and the jsii kernem is the following:

┏━━━━━━━━━━━┓                                                  ┏━━━━━━━━━━━━━━━┓
┃  Kernel   ┃                                                  ┃Runtime Library┃
┗━━━━━┳━━━━━┛                                                  ┗━━━━━━━┳━━━━━━━┛
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃
      ┃◀─┤  Create(FQN: "FooClass", Overrides=["baz", "reverse"])   ├──┃
      ┃  └──────────────────────────────────────────────────────────┘  ┃
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃
      ┃──┤                     OK(ObjID: "Foo")                     ├─▶┃
      ┃  └──────────────────────────────────────────────────────────┘  ┃
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃
      ┃◀─┤        InvokeRequest(ObjID: "Foo", Method: "bar")        ├──┃
      ┃  └──────────────────────────────────────────────────────────┘  ┃
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃
      ┃──┤ CallbackRequest(ID: 1, ObjID: "Foo", Invoke: "reverse")  ├─▶┃────┐
      ┃  └──────────────────────────────────────────────────────────┘  ┃    │call
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃    │obj("Foo").reverse()
      ┃◀─┤            Complete(CallbackID: 1, OK: true)             ├──┃◀───┘
      ┃  └──────────────────────────────────────────────────────────┘  ┃
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃
      ┃──┤     CallbackRequest(ID: 2, ObjID: "Foo", Get: "baz")     ├─▶┃────┐
      ┃  └──────────────────────────────────────────────────────────┘  ┃    │get
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃    │obj("Foo").baz
      ┃◀─┤            Complete(CallbackID: 2, OK: "baz")            ├──┃◀───┘
      ┃  └──────────────────────────────────────────────────────────┘  ┃
      ┃  ┌──────────────────────────────────────────────────────────┐  ┃
      ┃──┤                InvokeResponse(OK: "zab")                 ├─▶┃
      ┃  └──────────────────────────────────────────────────────────┘  ┃
      ┃                                                                ┃

See Also


Last update: 2021-09-24