Language Guide🔌 Extensibility

🔌 Extensibility

KodiScript is designed to be extensible. You can enrich the language by adding your own native functions, allowing scripts to interact with your system while maintaining full control over what’s exposed.

Why Extensibility?

The built-in native functions cover common use cases, but your application likely has unique requirements:

  • Database access: Let scripts fetch users, orders, or any data
  • Notifications: Send emails, push notifications, SMS from scripts
  • Business logic: Calculate discounts, validate rules, process workflows
  • External APIs: Call third-party services securely
  • File operations: Read/write files with proper permissions

Function Registration

Each SDK provides a registerFunction method that lets you add custom functions callable from KodiScript:

val result = KodiScript.builder("""
    let user = fetchUser(123)
    let discount = calculateDiscount(user.tier, 100)
    print("Discount: " + discount + "%")
""")
    .registerFunction("fetchUser") { args ->
        // Call your database
        mapOf("id" to args[0], "name" to "Alice", "tier" to "gold")
    }
    .registerFunction("calculateDiscount") { args ->
        val tier = args[0] as String
        when (tier) {
            "gold" -> 20
            "silver" -> 10
            else -> 5
        }
    }
    .execute()

Object Binding v0.1.1

Starting from version 0.1.1, you can bind entire objects to the script context. This allows scripts to access properties and call methods on your native objects directly, without wrapping each method in a registerFunction call.

class UserService(private val db: Database) {
    fun findUser(id: Int): User? = db.findUser(id)
    fun createUser(name: String, email: String): User = db.createUser(name, email)
    val userCount: Int get() = db.countUsers()
}
 
val service = UserService(myDatabase)
 
val result = KodiScript.builder("""
    let user = service.findUser(42)
    let total = service.userCount
    print("Found user: " + user.name)
""")
    .bind("service", service)  // Bind the entire object
    .execute()

When to Use bind vs registerFunction

ApproachUse Case
bind()Expose an entire service or object with multiple methods/properties
registerFunction()Expose individual functions with custom logic or validation

You can combine both approaches:

KodiScript.builder(script)
    .bind("userService", userService)
    .bind("cartService", cartService)
    .registerFunction("calculateTax") { args -> taxCalculator.compute(args) }
    .execute()

Real-World Example: E-commerce Rules Engine

Imagine letting your business team define discount rules without deploying code:

// Script stored in database, editable by admins
let user = getUser(userId)
let cart = getCart(cartId)
 
let discount = 0
 
// Gold members get 20% off
if (user.tier == "gold") {
    discount = 20
}
 
// Extra 5% for orders over $100
if (cart.total > 100) {
    discount = discount + 5
}
 
// Holiday special
if (isHolidaySeason()) {
    discount = min(discount + 10, 50)  // Cap at 50%
}
 
// Apply and notify
let finalPrice = cart.total * (1 - discount / 100)
sendNotification(user.email, "Your discount: " + discount + "%")
 
return { discount: discount, finalPrice: finalPrice }

Your backend exposes only the functions you choose:

KodiScript.builder(adminScript)
    .withVariable("userId", request.userId)
    .withVariable("cartId", request.cartId)
    .registerFunction("getUser") { userService.findById(it[0] as Long) }
    .registerFunction("getCart") { cartService.findById(it[0] as Long) }
    .registerFunction("isHolidaySeason") { holidayService.isActive() }
    .registerFunction("sendNotification") { notificationService.send(it[0], it[1]) }
    .execute()

Security Best Practices

When exposing functions to scripts:

  1. Validate inputs - Never trust script arguments blindly
  2. Limit scope - Only expose what’s necessary
  3. Rate limit - Prevent abuse of expensive operations
  4. Audit logs - Track what scripts execute
  5. Sandbox - Scripts can’t access anything you don’t explicitly provide
// ✅ Good: Scoped, validated function
.registerFunction("getUser") { args ->
    val id = (args[0] as? Number)?.toLong() 
        ?: throw IllegalArgumentException("Invalid user ID")
    userService.findById(id)?.toSafeMap() // Only expose safe fields
}
 
// ❌ Bad: Too permissive
.registerFunction("query") { args ->
    database.executeRaw(args[0] as String) // SQL injection risk!
}

Use Cases

Use CaseCustom Functions
E-commercegetUser, getCart, applyDiscount, sendEmail
Workflow Engineapprove, reject, escalate, notify
Data ProcessingfetchRecord, transform, validate, save
IoT/AutomationreadSensor, setDevice, scheduleTask
GaminggetPlayer, giveReward, checkAchievement

Next Steps