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 greet: AutolangNativeFunc = (name) => {
return `Hello, ${name}!`;
};
const add: AutolangNativeFunc = (a, b) => {
return (a as number) + (b as number);
};
const config: LibraryConfig = {
autoImport: true, // available in all files without @import
allowLateinitKeyword: false,
allowNonNullAssertion: false,
};
compiler.registerBuiltInLibrary(
"my/utils",
`
@native("greet")
fun greet(name: String): String
@native("add")
fun add(a: Int, b: Int): Int
`,
config,
{ greet, add }
);
await compiler.compileAndRun("main.atl", `
println(greet("World"))
println(add(10, 32))
`);
console.log(compiler.getOutput());
// Hello, World!
// 42How 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 query builder, a database connection, 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 APIs 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(
"company/database", `
class Product (
val id: Int,
val name: String,
val price: Float
)
@js_object
class Query {
@native("query_where")
fun where(field: String, value: String): Query
@native("query_limit")
fun limit(count: Int): Query
@native("query_exec")
fun execute(): Array<Product>
}
class Database {
@native("create_query")
static fun createQuery(): Query
}
`,
{ autoImport: true },
{
create_query() {
return new JSQueryBuilder();
},
query_where(field, value) {
// "this" is the native JSQueryBuilder instance
return this.where(field, value);
},
query_limit(count) {
return this.limit(count);
},
query_exec() {
return this.execute();
}
}
);
await compiler.compileAndRun("main.atl", `
val products = Database.createQuery()
.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 Query Builder.
๐ก๏ธ 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
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.
