JavaScript Types in Native Code
Vega Turbo Modules support a variety of native types which can be converted to and from JavaScript in Turbo Module parameter and return values. This means that, for example, a Turbo Module method which accepts std::string can be called from JavaScript with a JavaScript string, and the argument will be automatically converted; similarly, a Turbo Module method which returns std::string will return a JavaScript string to the JavaScript environment. Some of these, such as std::string and int32_t, are basic C++ types. Others are specific to Vega, and are primarily available as part of the com::amazon::kepler::turbomodule namespace.
Types map
The following table shows the mapping between TypeScript types and their C++ counterparts. This is the mapping used by the codegen tool.
Some of these types are not part of base TypeScript and need to be imported from @amazon-devices/keplerscript-turbomodule-api.
| TypeScript type | Native type |
|---|---|
string |
std::string |
number |
double |
Int32 |
int32_t |
UInt32 |
uint32_t |
Int64[1] |
int64_t |
Double |
double |
Float |
float |
boolean |
bool |
void |
void |
Array<T>, T[] (homogeneous)[2] |
std::vector<T> |
Array |
JSArray |
Object |
JSObject |
Promise |
Promise |
Function (parameter type) |
Callback |
Function (return type) |
HostCallback |
bigint |
BigInt[3] |
ArrayBuffer |
ArrayBuffer |
NativeObject |
std::shared_ptr<NativeObject> |
*JsonContainer[4] |
utils::json::JsonContainer |
[1] int64_t might have loss of precision due to Number.MAX_SAFE_INTEGER. You can use BigInt for large integers where the precision is necessary.
[2] A homogeneous array is the one whose elements are of the same type.
[3] BigInt is supported for signed and unsigned 64-bit integers, as direct method parameter and return types. For more details on supported usages, see BigInt
[4] JsonContainer will be removed after the gaps in JSObject are addressed. If you need to use it, you can import JsonContainer type from @amazon-devices/keplerscript-turbomodule-api for a generic Object, or define a custom type alias such as
type MyJsonContainer = {myProperty: string};
Note: For integers larger than JavaScript's Number.MAX_SAFE_INTEGER, use a std::string representation for the Turbo Module's parameter and return types. You can convert the string to more appropriate types within the JavaScript and C++ implementations.
JSArray
For homogeneous (T[]) arrays, you can use std::vector<T>. For other arrays, you can use JSArray.
A JSArray is an std::vector which accepts a variety of element types including null pointer, boolean, various int, double, float, std::string, JSArray, and JSObject. Its usage is therefore similar to std::vector.
Example
JSArray SampleTurboModule::getJSArray() {
auto jsObject = JSObject{};
auto jsArray = JSArray{};
jsArray.emplace_back(jsObject);
jsArray.emplace_back(std::string{"foo"});
jsArray.emplace_back(3.14);
return jsArray;
}
JSArray SampleTurboModule::extendJSArray(JSArray arr) {
arr.emplace_back(std::string{"new element"});
return arr;
}
JSObject
A JSObject is an std::map with std::string keys which accepts a variety of value types including null pointer, boolean, various int, double, float, std::string, JSArray, and JSObject. Its usage is therefore similar to std::map.
JSObject provides a static parseJson method. The parseJson method accepts an std::string JSON string and returns the equivalent JSObject.
Example
JSObject SampleTurboModule::getJSObject(std::string jsonContent) {
auto jsObject = JSObject::parseJson(jsonContent);
return jsObject;
}
JSObject SampleTurboModule::getJSObject() {
auto jsObject = JSObject{};
jsObject.emplace("intValue", 100);
jsObject.emplace("doubleValue", 25.1);
jsObject.emplace("boolValue", true);
jsObject.emplace("strValue", std::string{"string value"});
return jsObject;
}
JSObject SampleTurboModule::extendJSObject(JSObject obj) {
obj.emplace("foo", std::string{"bar"});
return obj;
}
Proxy JSObject and JSArray
Vega Turbo Modules support the proxy types proxy::JSObject and proxy::JSArray in addition to JSObject and JSArray types. One of the key features of the proxy types is dynamic access and modification. You can read from and write to JavaScript objects and arrays directly from your C++ code, without having to create copies.
However, there are a few important things to keep in mind when working with these proxy types:
-
Thread Considerations:
The proxy types must be used within the JSThread context and the scope of a Turbo Module method invocation. They can't be stored or accessed from arbitrary threads. Thread safety checks are only performed in Debug mode, so be cautious when working with these proxy objects in a multi-threaded environment.
-
Native construction:
You can natively construct new proxy items, but you'll need to obtain the required JSRuntime as a method parameter from your Turbo Module or HostCallback methods.
Warning: Don't use default constructors. the default constructors for these proxy types are only maintained to support current internal usage in constructs such as
std::tupleandstd::variant. You shouldn't rely on these default constructors, as they require a JSThread context and will be removed in a future release.
JSRuntime
The JSRuntime type represents a JavaScript runtime environment. It's a native-only type that can be requested as a parameter by any Turbo Module or HostCallback method. It is typically used for creating new proxy items in C++.
Note: JSRuntime cannot be constructed from a non-JSThread.
void SampleTurboModuleV2::sampleMethod(JSRuntime rt) {
// Creating a new proxy object
auto newObject = proxy::JSObject(rt);
// Creating a proxy array
auto newArray = proxy::JSArray(rt);
}
proxy::JSObject
The proxy::JSObject type allows you to create a JavaScript object that can be passed to the native code, and have its properties and methods dynamically accessed and modified from the native side without creating a copy.
Note: Access to proxy::JSObject is restricted to the JSThread context and must occur within the scope of a Turbo Module method invocation. Thread safety checks are only performed in Debug mode.
std::vector<proxy::JSObject> SampleTurboModuleV2::getProxyObject(JSRuntime rt, std::vector<proxy::JSObject> args) {
// Reading properties from received object
auto object = args[0];
auto propertyNames = object.getPropertyNames();
for (const auto& propertyName: propertyNames) {
auto propertyValue = object.getProperty(propertyName);
if (std::holds_alternative<std::string>(propertyValue)) {
auto stringValue = std::get<std::string>(propertyValue);
TMINFO("Property " + propertyName + " = " + stringValue);
}
}
// Creating and returning new object
auto newObject = proxy::JSObject(rt);
newObject.setProperty("stringProp", "foo");
newObject.setProperty("numberProp", 1.5);
newObject.setProperty("boolProp", true);
args[0] = newObject;
return args;
}
proxy::JSArray
The proxy::JSArray type allows you to create a JavaScript array that you can pass to the native code, and have its elements dynamically accessed and modified from the native side.
Note: Access to proxy::JSArray is restricted to the JSThread context and must occur within the scope of a Turbo Module method invocation. Thread safety checks are only performed in Debug mode.
proxy::JSArray SampleTurboModuleV2::getProxyArray(proxy::JSArray arg) {
size_t index = 0;
for(const auto& element: arg) {
if (std::holds_alternative<std::string>(element)) {
auto elementValue = std::get<std::string>(element);
TMDEBUG("SampleTurboModuleV2::getProxyArray initial string element = " + elementValue);
arg.set(index, "C++String");
}
++index;
}
return arg;
}
Promise
Promises should be executed on a separate thread.
A Promise is constructed with a PromiseOperation as input. A PromiseOperation is a function which accepts an std::shared_ptr<Promise> and returns void.
A Promise has resolve and reject methods. A Promise can be rejected with an Error or std::string, and resolved with any of the C++ Turbo Module types from the types map.
Example
Promise<std::string> SampleTurboModule::getValueWithPromise(bool error) {
return Promise([self = this, error](const std::shared_ptr<Promise>& promise) {
std::thread([error, promise]() {
if (error) {
promise->reject(Error("promise rejected using Error class"));
} else {
promise->resolve(std::string{"result!"});
}
}).detach();;
});
}
Callback
A callback should be executed on a separate thread.
A Callback object can only be a parameter value, representing a JavaScript callback passed to C++. See HostCallback for callbacks as a return type.
The only method exposed by the Callback object is invoke which calls the JavaScript callback with the specified arguments, and returns the result to C++.
Example
// JS
SampleTurboModule.getValueWithCallback((value : number) => {
console.log(value); // this should log the value 5, which is passed as an argument from the C++ Callback.invoke method.
return 2 * value;
});
// C++
void SampleTurboModule::getValueWithCallback(Callback callback) {
std::thread([](Callback callback) {
auto future = callback.invoke(5);
std::string returnValue{"undefined"};
try {
auto jsValue = future.get();
// Any number returned from JS is always a double
if(std::holds_alternative<double>(jsValue)) {
returnValue = std::to_string(std::get<double>(jsValue));
}
} catch (const std::exception& ex) {
TMERROR("Error getting the return value from callback. Exception: " + std::string(ex.what()));
}
TMINFO("Return value from callback is: " + returnValue);
}, callback).detach();
}
HostCallback
HostCallback can only be a return value, used to return a C++ callback to the JavaScript environment. See Callback for callbacks as a parameter type.
Example
// C++
std::string sampleHostCallbackMethod(std::string jsInputString) {
return "Received JS string " + jsInputString);
}
HostCallback SampleTurboModule::getHostCallback() {
return HostCallback(sampleHostCallbackMethod);
}
// JS
const hostCallback = SampleTurboModule.getHostCallback();
console.log(hostCallback("Hello from HostCallback!")); // prints "received JS string Hello from HostCallback!"
}
You can also invoke a Callback using a HostCallback.
// JS
SampleTurboModule.invokeCallbackWithHostCallback((hostCallback: (obj: Object) => number): void {
const secret = hostCallback();
console.log(secret); // logs 17
});
// C++
void SampleTurboModule::invokeCallbackWithHostCallback(Callback callback) {
auto secret = 17;
auto lambda = [secret]() {
return secret;
};
auto hostCallback = HostCallback(std::function<int()>(lambda));
callback.invoke(hostCallback);
}
ArrayBuffer
An ArrayBuffer represents a generic, fixed-length raw binary data buffer.
Example
ArrayBuffer SampleTurboModule::getNativeMemoryArrayBuffer() {
return {"Hello World!"};
}
ArrayBuffer SampleTurboModule::modifyArrayBuffer(ArrayBuffer buf) {
// No copy of data since it's just modification without inserting new content
uint8_t* data = buf.data();
size_t size = buf.size();
for (auto i = 0; i < size; i++) {
data[i] = 9;
}
return buf;
}
ArrayBuffer SampleTurboModule::insertSecretData(ArrayBuffer buf) {
// Insert here creates a copy of the data and ataches a finalizer
uint8_t secret[] = {6, 7, 9, 9, 0};
arg.insert(5, secret);
return arg;
}
BigInt
A BigInt represents an integer. In JavaScript, these are used for integers which exceed Number.MAX_SAFE_INTEGER or Number.MIN_SAFE_INTEGER. In Vega, Turbo Modules support BigInt with int64_t- and uint64_t-based getter and constructor methods.
BigInt SampleTurboModule::getBigInt(BigInt bigint) {
// Create BigInts
auto big1 = BigInt::fromI64(-9007199254740990);
auto big2 = BigInt::fromUi64(static_cast<uint64_t>(9007199254740990));
// Access values. Getters are not dependent on the type used in construction.
auto asI64 = bigint.getI64();
auto asUi64 = bigint.getUi64();
// getI64() returns a struct with the following properties
int64_t i64value = asI64.value;
bool lossless = asI64.lossless;
// getUi64() returns a struct with the following properties
bool is_signed = asUi64.is_signed;
uint64_t ui64value = asUi64.value;
bool lossless = asUi64.lossless;
return big1;
}
You can use BigInt in method parameter and returns, as well as Callbacks and Promises, but only as a raw value and not as a part of a JSArray or JSObject.
A JavaScript BigInt which is too large to be represented by the Vega Turbo Module BigInt type (for example, it cannot be represented by int64_t or uint64_t) will fail an assertion, as do all invalid parameters for Vega Turbo Modules. For more information, see the error handling in "Advanced Turbo Module topics".
utils::json::JsonContainer
Use of JsonContainer for the JSObject type is being replaced, but there are currently still some feature gaps. For a full list of supported types, see the types map.
Example
utils::json::JsonContainer SampleTurboModuleV2::getJsonContainer() {
auto result = utils::json::JsonContainer::createJsonObject();
result.insert("const1", true);
result.insert("const2", 375);
result.insert("const3", std::string("something"));
return result;
}
NativeObject
In most cases, when you are passing data from your native code to JavaScript, a JSONContainer or JSObject will suffice. However, if you need a native object whose methods can be invoked from JavaScript, you need to create an object that implements com::amazon::kepler::turbomodule::NativeObject.
Example
#include <Vega/turbomodule/NativeObject.h>
using namespace com::amazon::kepler::turbomodule;
class SampleNativeObject : NativeObject {
public:
// Methods must be added to the methodAggregator in this function in order to be exposed to JavaScript.
void aggregateMethods(MethodAggregator<NativeObject> &methodAggregator) noexcept override {
methodAggregator.addMethod("getString", 1,
&SampleNativeObjectV2::getString);
}
std::string getString(std::string arg) {
return "Goodbye " + arg;
}
};
// in TurboModule implementation:
std::shared_ptr<NativeObject> SampleTurboModule::getNativeObject() {
return std::make_shared<SampleNativeObject>();
}
// Usage in JavaScript:
SampleTurboModule.getNativeObject().getString("foo"); // returns "Goodbye foo"
Note that NativeObject is only supported as a Turbo Module method return type, not a parameter.
APMF Types
IDLs use some APMF types which are not always directly convertible between the supported Turbo Module parameter/return types. You might need to implement custom logic to do these conversions.
Related topics
Last updated: Sep 30, 2025

