jsMain.internal.ValueTransformations.kt Maven / Gradle / Ivy
package pt.lightweightform.lfkotlin.internal
import pt.lightweightform.lfkotlin.Schema
import pt.lightweightform.lfkotlin.schemas.ClassSchema
/**
* Transforms a Kotlin [value] associated with a given [schema] into a value accepted by LF. This is
* used to transform initial values, computed values, and allowed values.
*/
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UNCHECKED_CAST")
internal fun ktToJsValue(schema: Schema, value: Any?): Any? {
if (value == null) {
return null
}
return when (schema.type) {
"list", "table" -> {
val childrenSchema =
(if (schema.type == "list") schema.asDynamic().elementsSchema
else schema.asDynamic().rowsSchema) as
Schema
value.asDynamic().map { el -> ktToJsValue(childrenSchema, el) }
}
"record" -> {
val childInfoByName = (schema as ClassSchema).childInfoByName
val record = js("{}")
for ((name, info) in childInfoByName) {
record[name] = ktToJsValue(info.schema as Schema, info.prop.get(value))
}
record
}
"tuple" -> {
val elementsSchemas = schema.asDynamic().elementsSchemas as Array>
value.asDynamic().map { el, i: Int -> ktToJsValue(elementsSchemas[i], el) }
}
else -> value
}
}
/**
* Transforms a JS [value] (used within LF) associated with a given [schema] into a Kotlin value.
* This is used when calling `get` on the context.
*/
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UNCHECKED_CAST")
internal fun jsToKtValue(schema: Schema, value: Any?): Any? {
// Values that don't need to be transformed
if (value == null || schema.type !in arrayOf("list", "record", "table", "tuple")) {
return value
}
// Record values need to be mapped to their respective Kotlin classes, this has the unfortunate
// downside that we're recursively creating MobX dependencies to all record fields (although we
// avoid depending on all elements of lists via the use of proxies, as explained below).
if (schema.type == "record") {
val kClass = (schema as ClassSchema).kClass
val arguments = mutableMapOf()
// NOTE: In order to not create MobX dependencies, what I wanted to do here was to wrap the
// creation of the class instance in a `MobX.untracked` and then proxy said instance by
// causing each member access to access its correspondent MobX object property. However, I
// couldn't find a way of mapping "compiled" field names to record field names (the IR
// compiler will compile the record field `X` to something like `_X` in the class instance,
// and I couldn't find a clean way of mapping `_X` to `X` that didn't depend on compilation
// assumptions).
for ((name, info) in schema.childInfoByName) {
arguments[name] = jsToKtValue(info.schema as Schema, value.asDynamic()[name])
}
return if (schema.constructorFunction != null) schema.constructorFunction!!(arguments)
else constructFromKClass(kClass, *arguments.values.toTypedArray())
}
// We don't want to iterate through all elements of a list (list, table, and tuple schemas) and
// transform them recursively as doing so, besides potentially expensive, would create MobX
// dependencies to all accessed elements. To avoid this, we return a proxy that defers the
// transformations until the user accesses elements of the list.
return proxyGet(value) { target: dynamic, prop: String, receiver: dynamic ->
// Return whatever was being queried when `prop` isn't an index
val idx = prop.toIntOrNull()
if (idx == null || idx < 0) {
return@proxyGet Reflect.get(target, prop, receiver)
}
when (schema.type) {
"list", "table" -> {
val childrenSchema =
(if (schema.type == "list") schema.asDynamic().elementsSchema
else schema.asDynamic().rowsSchema) as
Schema
jsToKtValue(childrenSchema, target[idx])
}
"tuple" -> {
val elementsSchemas = schema.asDynamic().elementsSchemas as Array>
if (idx >= elementsSchemas.size) {
return@proxyGet Reflect.get(target, prop, receiver)
}
jsToKtValue(elementsSchemas[idx], target[idx])
}
else -> error("Invalid schema")
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy