Native Libraries
This is how you give scripts access to your own code. You define a typed interface in Autolang, back it with JavaScript functions, and the VM calls them when scripts invoke those declarations.
If you haven't set up the compiler yet, start with the npm Integration Guide first.
Registering a Native Library
Use registerBuiltInLibrary() to declare a library. You pass it a name, the Autolang source that defines the interface (functions and classes), a LibraryConfig, and the actual JavaScript implementations.
import { ACompiler, AutolangNativeFunc, LibraryConfig } from 'autolang-compiler';
const compiler = await ACompiler.create();
const calculateDiscount: AutolangNativeFunc = (price, discountPercent) => {
const p = price as number;
const d = discountPercent as number;
return Math.round(p * (1 - d / 100));
};
const formatReceipt: AutolangNativeFunc = (orderId, total) => {
return `Receipt #${orderId}: Total $${total}`;
};
const config: LibraryConfig = {
autoImport: true, // available in all files without @import
allowLateinitKeyword: false,
allowNonNullAssertion: false,
};
compiler.registerBuiltInLibrary(
"sales/pricing",
`
@native("calculateDiscount")
fun calculateDiscount(price: Int, discountPercent: Int): Int
@native("formatReceipt")
fun formatReceipt(orderId: String, total: Int): String
`,
config,
{ calculateDiscount, formatReceipt }
);
await compiler.compileAndRun("main.atl", `
val discounted = calculateDiscount(100, 15)
println(formatReceipt("ORD-8492", discounted))
`);
console.log(compiler.getOutput());
// Receipt #ORD-8492: Total $85How arguments are passed
thisArg is injected unless the function belongs to a @js_object class instance - in that case the VM binds the instance to JavaScript's this.For a full listing of LibraryConfig options and all exported types, see the Type Reference.
Wrapping JavaScript Objects with @js_object
When you need to pass a JavaScript object back and forth between the host and scripts (a domain service filter, a stateful builder, a stream), annotate the Autolang class with @js_object. This tells the compiler the class is a thin typed wrapper around a native JS object - the VM preserves the reference and binds it to this when calling methods.
This lets you build clean, fluent interfaces in Autolang (like .where().limit().execute()) while keeping the underlying JS object intact on the host side.
Use method shorthand, not arrow functions
@js_object methods rely on JavaScript's this binding, you must use standard function syntax or ES6 method shorthand when implementing them. Arrow functions (() => {}) capture this from the outer scope and will not work correctly.compiler.registerBuiltInLibrary(
"inventory/catalog", `
class Product (
val id: Int,
val name: String,
val price: Float
)
@js_object
class CatalogFilter {
@native("filter_where")
fun where(field: String, value: String): CatalogFilter
@native("filter_limit")
fun limit(count: Int): CatalogFilter
@native("filter_exec")
fun execute(): Array<Product>
}
class CatalogService {
@native("create_filter")
static fun createFilter(): CatalogFilter
}
`,
{ autoImport: true },
{
create_filter() {
return new HostCatalogFilter();
},
filter_where(field, value) {
// "this" is the native HostCatalogFilter instance
return this.where(field, value);
},
filter_limit(count) {
return this.limit(count);
},
filter_exec() {
return this.execute();
}
}
);
await compiler.compileAndRun("main.atl", `
val products = CatalogService.createFilter()
.where("category", "organic")
.limit(3)
.execute()
products.forEach {|p| println("- ${p.name}: $${p.price}") }
`);See a full working example with this pattern at Examples - Fluent Order Filter Interface.
Handling Errors in Native Functions
If a native JavaScript function throws an error, the Autolang VM automatically catches it and wraps it into an Autolang Exception (prefixed with JSException:). You can catch this error inside the Autolang script using a standard try-catch block, or retrieve it from the host compiler after execution using compiler.hasException() and compiler.getException().
import { ACompiler } from 'autolang-compiler';
const compiler = await ACompiler.create();
compiler.registerBuiltInLibrary(
"my/error_lib", `
@native("throwError")
fun throwError(): Void
`,
{ autoImport: true },
{
throwError() {
throw new Error("Something went wrong in JavaScript!");
}
}
);
// Option 1: Catching the JS error inside Autolang
await compiler.compileAndRun("main.atl", `
try {
throwError()
} catch (e) {
println("Caught error inside script: " + e.message)
}
`);
console.log(compiler.getOutput());
// Caught error inside script: JSException: Error: Something went wrong in JavaScript!
// Option 2: Let it propagate and handle it on the host side
await compiler.compileAndRun("main2.atl", `
throwError()
`);
if (compiler.hasException()) {
const exception = compiler.getException();
console.error("Host caught VM Exception:", exception?.message);
// Host caught VM Exception: JSException: Error: Something went wrong in JavaScript!
// at throwError ...
}Error and Warning Callbacks
Instead of polling for errors after each run, you can subscribe to compiler diagnostics in real time. These callbacks fire during compilation - not at runtime - so they're useful for logging or surfacing issues to users immediately.
import { ACompiler } from 'autolang-compiler';
const compiler = await ACompiler.create();
compiler.setOnError((message: string) => {
console.error("[Autolang Error]", message);
});
compiler.setOnWarning((message: string) => {
console.warn("[Autolang Warning]", message);
});
// This will trigger the error callback
await compiler.compile("main.atl", `
val x: Int = "not a number"
`);
// Pass null to unsubscribe
compiler.setOnError(null);
compiler.setOnWarning(null);To control what the script can access (HTTP, file system), see Security & Sandboxing.