All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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