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::tuple
andstd::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