npm Packages for Koru

· 4 min read

Koru Gets a Package Manager

Today we shipped something that makes Koru feel like a real language: npm package support.

You can now:

  1. Declare npm dependencies directly in your Koru source
  2. Install them with koruc app.kz i
  3. Import and use them like any other module

Here’s what it looks like:

~import "$std/package"
~import "$koru/sqlite3"

~std.package:requires.npm { "@korulang/sqlite3": "^0.0.1" }

~koru.sqlite3:open(path: ":memory:")
| db conn |> koru.sqlite3:close(conn)
    | closed |> std.io:print.ln("It works!")
| err e |> std.io:print.ln("Error: " ++ e.msg)

That’s it. Your dependencies live in your source code, not in a separate manifest file you have to keep in sync.

The First Package: @korulang/sqlite3

We published @korulang/sqlite3 to npm as a proof of concept. It’s a real SQLite binding with phantom type obligations:

~pub event open { path: []const u8 }
| db { conn: *Connection[opened!] }
| err { code: i32, msg: []const u8 }

The [opened!] phantom annotation means: “this connection carries an opened obligation that must be discharged.” You can’t forget to close it - the compiler will catch you.

How It Works

1. Project Setup

Run koruc init in an empty directory:

$ koruc init

This creates koru.json with path aliases:

{
  "name": "my-project",
  "version": "0.1.0",
  "entry": "app.kz",
  "paths": {
    "node": "./node_modules",
    "koru": "./node_modules/@korulang"
  }
}

The $koru alias maps to ./node_modules/@korulang, so ~import "$koru/sqlite3" resolves to ./node_modules/@korulang/sqlite3/index.kz.

2. Declare Dependencies

In your Koru source, declare what you need:

~import "$std/package"

~std.package:requires.npm { "@korulang/sqlite3": "^0.0.1" }

This is a compile-time declaration. It doesn’t execute at runtime - it tells the compiler what packages your code needs.

3. Install

$ koruc app.kz i
Found 1 npm package declaration(s)
✓ Generated package.json
✓ npm install complete

The compiler walks your AST, collects all requires.npm declarations, generates package.json, and runs npm install.

4. Use

Now you can import the package:

~import "$koru/sqlite3"

~koru.sqlite3:open(path: "test.db")
| db d |> ...

The imported module’s events become available under the koru.sqlite3 namespace.

Why npm?

We considered building a custom package registry, but npm already exists and works well. By using npm:

  • Package authors get familiar tooling (npm publish)
  • Users get familiar installation (npm install under the hood)
  • We get versioning, namespacing (@korulang/*), and distribution for free

Koru packages are just npm packages with .kz files instead of .js files.

What’s Next

This is the foundation. Now we can build:

  • More stdlib packages - graphics, networking, parsing
  • Third-party ecosystem - anyone can publish @username/package
  • Native dependency handling - ~std.build:requires for linking C libraries

The @korulang/sqlite3 package already demonstrates native dependencies:

~std.build:requires {
    exe.linkSystemLibrary("sqlite3");
    exe.linkLibC();
}

This tells the Zig build system to link against SQLite. Build requirements and package requirements work together.

Try It

# Get Koru (if you haven't)
# ... installation instructions ...

# Create a project
mkdir my-project && cd my-project
koruc init

# Edit app.kz to use sqlite3
# ... add the code above ...

# Install and run
koruc app.kz i
koruc run app.kz

The package ecosystem is live. Go build something.