Import = Install
The Dream
What if dependencies worked like this?
~import "$koru/sqlite3"
~koru.sqlite3:open(path: ":memory:")
| db d |> koru.sqlite3:close(conn: d.conn)
| closed |> std.io:print.ln("It works!") No package.json. No requirements.txt. No go.mod. Just the import.
Today, this works.
How It Works
When you write ~import "$koru/sqlite3", the resolver tries a fallback chain:
1. ./node_modules/@korulang/sqlite3 → Check here first
2. /path/to/koru_registry/sqlite3 → Fall back to registry If the npm package is installed, use it. If not, use the registry thunk.
The Registry Thunk
A thunk is a tiny .kz file that declares how to get a package:
// koru_registry/sqlite3/index.kz
~import "$std/package"
~std.package:requires.npm { "@korulang/sqlite3": "^0.0.1" } That’s the entire file. It doesn’t contain the actual sqlite3 code - it just tells the compiler where to get it.
The Flow
┌─────────────────────────────────────────────────────────────┐
│ 1. User writes: ~import "$koru/sqlite3" │
│ │
│ 2. koruc app.kz │
│ ├─ Resolver: npm not installed, use thunk │
│ ├─ Collect: requires.npm from thunk │
│ └─ Generate: package.json │
│ │
│ 3. npm install │
│ │
│ 4. koruc app.kz (again) │
│ ├─ Resolver: npm package found! │
│ └─ Compile: with real sqlite3 events │
│ │
│ 5. Remove import → dependency vanishes everywhere │
└─────────────────────────────────────────────────────────────┘ The Elegant Part: Fallback Chains
This isn’t a special $koru feature. It’s a general mechanism.
In koru.json, paths can now be arrays:
{
"paths": {
"koru": [
"./node_modules/@korulang",
"/usr/local/lib/koru_registry"
]
}
} The resolver tries each path in order until one exists. First match wins.
This means:
- Installed packages take priority - npm packages shadow registry thunks
- No special cases - just the general fallback mechanism
- Works for any alias - not just
$koru
Why This Matters
Source as Single Source of Truth
Your dependencies are in your source code. Not in a manifest file that drifts out of sync.
// This IS the dependency declaration
~import "$koru/sqlite3" Dead Code Elimination Eliminates Dependencies
Remove an import? The dependency vanishes from:
package.json(regenerated on next compile)build.zig(no morelinkSystemLibrary("sqlite3"))- The binary (no dead code)
Everything else is a clown show where you manually clean up orphaned dependencies.
Version Control
Want the registry’s recommended version?
~import "$koru/sqlite3" // Registry controls version Want to pin an exact version?
~std.package:requires.npm { "@korulang/sqlite3": "0.0.1" } // Exact pin
~import "$node/@korulang/sqlite3" // Direct path Both work. Choose based on your needs.
Creating a Registry Thunk
Want to add a package to the registry? Create a thunk:
mkdir -p $KORU_REGISTRY/mypackage // $KORU_REGISTRY/mypackage/index.kz
~import "$std/package"
~std.package:requires.npm { "@korulang/mypackage": "^1.0.0" } Now ~import "$koru/mypackage" works for everyone with access to that registry.
Beyond npm
The thunk pattern works for any package source:
// Cargo (Rust)
~std.package:requires.cargo { "serde": "1.0" }
// Go modules
~std.package:requires.go { "github.com/user/pkg": "v1.0.0" }
// pip (Python)
~std.package:requires.pip { "requests": ">=2.28.0" }
// Git (direct)
~std.package:requires.git {
url: "https://github.com/korulang/some-lib",
ref: "v1.0.0"
} The registry thunk is just metadata. The compiler knows how to fetch from each source.
The Philosophy
Import = Install = Truth
Your source code is the single source of truth. Dependencies, build requirements, type safety, phantom obligations - all flow from what you import.
No manifest drift. No orphaned dependencies. No separate package.json to maintain.
Just imports.
~import "$koru/sqlite3" // That's it. That's the whole dependency declaration.