External Modules#
Bialet supports importing external Wren modules from remote sources, so you can use community-created libraries without manually downloading and managing them. This page covers using them, authoring your own, and managing the cache.
Import Syntax#
1. GitHub Shorthand (Recommended)#
import "gh:owner/repo/path/to/file" for ClassName
Fetches from the main branch by default. Target a specific branch or tag
with @:
import "gh:owner/repo/path/to/file@branch-or-tag" for ClassName
// Import emoji utilities from main branch
import "gh:4lb0/emoji/emoji" for Emoji
// Import from specific version/branch (recommended for stability)
import "gh:4lb0/emoji/export@1.0" for Emoji as EmojiV1
// Import from a specific tag
import "gh:username/mylib/module@v2.1.0" for MyClass
The
.wrenextension is automatically appended — don’t include it in the import path.Default branch is
mainif you don’t specify one.Use
@branchor@tagto pin a specific version.
2. Full URL#
import "https://example.com/path/to/module.wren" for ClassName
You must include the
.wrenextension in the URL.The URL must return raw Wren source code, not HTML.
For GitHub, use
raw.githubusercontent.comURLs.
import "https://raw.githubusercontent.com/4lb0/emoji/main/emoji.wren" for Emoji
How It Works#
Cache check — Bialet checks whether the module is already cached in the
BIALET_REMOTE_MODULESdatabase table.Download — if not cached, it downloads the module via HTTP GET.
Validation — checks for a 2xx status code.
Store — the content is stored in the database, keyed by import path.
Load — the module is loaded and made available to your code.
The first load needs an internet connection; every load after that reads from the local database — fast, and works offline. Cached modules persist until you explicitly clear them.
⚠️ Pitfall: cached modules don’t auto-update. If you push a fix to the
mainbranch of an imported module, running instances keep using the old cached copy until you clear the cache. This is deliberate — treat it as a stability feature, not a bug.
When to Use an External Module#
Bialet’s philosophy here is the opposite of the Node ecosystem. Node
culture normalizes pulling in a package for a one-line function (is-odd,
left-pad) because npm install is nearly free and the dependency graph
is invisible day-to-day. In Bialet, every import "gh:..." is a real,
visible network fetch the first time it runs, a row cached forever in
your app’s SQLite file, and code that runs with the full privileges of
your app.
A one-line helper doesn’t clear that bar. If a function fits in a few
lines and has no dependencies of its own, write it directly in your domain
class or a local _app/*.wren file — it costs nothing to maintain, has no
network dependency, and can’t change or disappear out from under you.
Reach for an external import when the library does something genuinely substantial: nontrivial parsing, a sizable data set, an algorithm you don’t want to re-implement and verify yourself. Reserve it for real dependencies, not for saving five lines of typing.
Cache Management {#cache-management}#
External modules are cached in _db.sqlite3, table BIALET_REMOTE_MODULES:
CREATE TABLE IF NOT EXISTS BIALET_REMOTE_MODULES (
module TEXT PRIMARY KEY, -- The import path (e.g., "gh:user/repo/path")
content TEXT, -- The cached Wren source code
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
)
-- Clear all cached external modules (forces re-download on next import)
DELETE FROM BIALET_REMOTE_MODULES;
-- View all cached modules
SELECT module, createdAt FROM BIALET_REMOTE_MODULES;
-- Check cache size
SELECT COUNT(*), SUM(LENGTH(content)) as total_bytes
FROM BIALET_REMOTE_MODULES;
After clearing the cache, restart your Bialet application (or trigger a reload) to re-download the modules.
Refreshing a Module Programmatically#
Since the cache is just a table, you can clear a specific row from Wren
code with a normal Query object — no shell access needed:
`DELETE FROM BIALET_REMOTE_MODULES WHERE module = ?`.query("gh:yourorg/internal-lib/utils@main")
The next time any request imports that module, the cache miss triggers a fresh HTTP download. This is most useful for internal libraries you control, shared across several Bialet apps, where you want changes to propagate without touching every consumer’s database by hand. Trigger it from a periodic cron job:
// _cron.wren
// Pull the latest internal shared library once a day at 3 AM.
Cron.at(3, 0) { |d|
`DELETE FROM BIALET_REMOTE_MODULES WHERE module = ?`
.query("gh:yourorg/internal-lib/utils@main")
}
Or from an admin-only route, so a person can force a refresh on demand:
// _app/admin/refresh-module.wren
if (Request.isPost) {
var mod = Request.post("module") || ""
if (mod != "") {
`DELETE FROM BIALET_REMOTE_MODULES WHERE module = ?`.query(mod)
}
return Response.redirect("/admin/refresh-module?done=1")
}
⚠️ Pitfall: this deliberately undoes the stability/security guarantee described above — a module can now change between requests without a deploy. Only wire this up for modules you own or fully trust, and never for third-party libraries pinned to
mainfor convenience.
GitHub URL Format#
gh:owner/repo/path@branch is internally converted to:
https://raw.githubusercontent.com/owner/repo/refs/heads/branch/path.wren
Import Statement |
Generated URL |
|---|---|
|
|
|
|
The
.wrenextension is added automatically.Default branch is
mainif unspecified.The path must resolve to a valid Wren file in the repository.
Invalid paths or missing files trigger error messages in the logs.
Error Handling#
“Invalid GitHub URL” — the import path doesn’t follow
gh:owner/repo/path; owner, repo, or file path is missing.
“Module not found in GitHub” — file doesn’t exist at that path, the branch/tag doesn’t exist, the HTTP request returned a non-2xx status, or there’s a network problem.
“Import type not supported” — the import uses a protocol other than
gh:, http://, or https://.
Check the Bialet logs for details:
System.print("Debug: attempting import...")
Security Considerations#
⚠️ Pitfall: external imports download and execute code from remote sources, with the same privileges as your application. Treat an import statement like adding a dependency, not like a link.
Verify the source before importing.
Review the code on GitHub when possible.
Use version tags, not
main, for stability and predictability.Cache behavior is a security feature — once downloaded, a module won’t silently change under you.
// ✅ Good: well-known, maintained library, pinned version
import "gh:4lb0/emoji/emoji@1.0" for Emoji
// ❌ Avoid: unknown source, unpinned, unverified
import "gh:random-user/suspicious-lib/module" for SomeClass
Multiple Versions#
import "gh:user/lib/module" for Module as ModuleLatest
import "gh:user/lib/module@v1.0" for Module as ModuleV1
// Use specific version based on your needs
var result = ModuleV1.someFunction()
Troubleshooting#
Module not found:
Visit the GitHub URL in a browser to confirm the path exists.
Check your internet connection (required for first-time downloads).
Confirm the branch name — defaults to
main, notmaster.
Import fails silently:
Check the Bialet server logs.
Confirm the module returns raw Wren code, not HTML or an error page.
Try the full URL format to isolate GitHub-shorthand issues.
Cached version is outdated:
DELETE FROM BIALET_REMOTE_MODULES WHERE module LIKE 'gh:user/repo%';
Then restart your Bialet application. Prefer version tags over branches going forward.
“Import type not supported”:
Use
gh:,http://, orhttps://only — noftp://, nofile://.Check for typos in the import statement.
Performance Tips#
Use version tags to avoid unnecessary cache invalidation.
Import only what you need — every import adds to first-load time.
Pre-cache modules in development before deploying.
Monitor cache size if you import many large modules.
Key Takeaways#
gh:shorthand or full URLs — both cached inBIALET_REMOTE_MODULES, never auto-updating once cached.Write small helpers locally — a one-line function doesn’t justify a remote dependency; reserve external imports for substantial libraries.
Relative imports inside a remote module don’t work — a module’s own internal imports must use full
gh:/URL syntax pointing at itself.You can force a refresh by deleting the module’s row from
BIALET_REMOTE_MODULESvia a normalQuery, but that trades away the stability guarantee — only do it for libraries you own.