Import = Install

· 5 min read

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 more linkSystemLibrary("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.