Keelv0.86.0
Docs / Helpers

Keel gives you a handful of global helper functions so you can reach the running application from anywhere — a route handler, a model, a plain function — without threading a container reference through every call. config('app.name'), cache().get(…), emit('user.registered', user): no this.app, no imports of the container.

They all resolve against the active application, which registers itself the moment an Application is constructed. In a normal single-app process — one Node server, or one Worker isolate — that's exactly the app you mean, so the globals just work.

import { config, cache, emit, logger, view } from "@shaferllc/keel/core";

const name = config<string>("app.name", "Keel");
const stats = await cache().remember("stats", 60, () => computeStats());
await emit("user.registered", user);
logger().info("welcome sent", { userId: user.id });

How they resolve

Every helper is sugar over app() — the one helper that returns the active Application. config() is app().make(Config).get(…); cache() is app().make(Cache); make() is app().make(…). So the whole set shares one precondition: an application must exist first. Call any helper before bootstrapping and app() throws:

No Keel application has been bootstrapped. Create an Application first.

In practice the application is created at boot, long before any request runs, so you never see this outside of a bare unit test that forgot to construct one.

The map

The helpers fall into groups, most of which have a dedicated guide. This page is the quick index — reach for the deep doc when you need the full surface.

Helper(s) What it reaches Deep dive
app the active Application this page
config configuration values configuration
bind singleton instance make bound the service container container
events emit listen the event emitter events
cache the cache cache
logger the logger logger
view the view renderer views

Container helpers, up close

The five container helpers let you register and resolve services from anywhere, exactly as app().bind(…) would — handy inside a factory or a helper function that has no container reference of its own:

import { singleton, make, bound } from "@shaferllc/keel/core";

singleton(Mailer, (app) => new Mailer(app.make(Config)));
const mailer = make(Mailer);
if (bound("clock")) { /* someone registered it */ }

The factory you pass to bind/singleton receives the container, so a service can resolve its own dependencies. Unlike the Container methods (which return this to chain), the bind/singleton helpers return void — there's no builder to chain off of at the global level. See container for the binding lifecycle, auto-resolution, and tokens.

Events, cache, logger

events(), cache(), and logger() each return the singleton service, so you call methods on the result:

import { events, cache, logger, listen, emit } from "@shaferllc/keel/core";

listen("order.paid", (order) => fulfil(order));   // subscribe
await emit("order.paid", order);                  // fan out, awaiting listeners
events().listenerCount("order.paid");             // the emitter itself

await cache().put("otp", code, 300);
logger().warn("retrying", { attempt: 2 });

emit and listen are shortcuts over events().emit / events().on, so you rarely need events() directly — reach for it when you want once, off, listenerCount, or clear. Full surface in events, cache, and logger.

Rendering a view

view() renders a component to a complete HTML document in one call — return it straight from a handler. Props are type-checked against the component:

import { view } from "@shaferllc/keel/core";

function Welcome({ appName }: { appName: string }) {
  return `<h1>Welcome to ${appName}</h1>`;
}

return view(Welcome, { appName: "Keel" });  // Promise<string>
return view(HomePage);                        // no props

See views for the component contract and async (Suspense) rendering.

These globals are the front door to services documented in depth elsewhere: configuration, the container, events, cache, logger, and views. Everything here is a thin, typed shortcut into one of those.


API reference

Every helper below is exported from @shaferllc/keel/core. All of them resolve against the active application and therefore throw No Keel application has been bootstrapped… if called before one is created.

app()

app(): Application

Returns the active Application — the container everything else resolves out of.

import { app } from "@shaferllc/keel/core";

const port = app().config().get<number>("app.port", 3000);

Notes: throws if no application has been constructed yet. Every other helper on this page is built on app(), so this is the single point where a "no application" error can originate.

config(key, fallback?)

config<T = unknown>(key: string, fallback?: T): T

Reads a configuration value by dot-path, returning fallback when the path is missing.

config<string>("app.name");
config("app.port", 3000);   // 3000 if unset

Notes: shorthand for app().make(Config).get(key, fallback). Read-only — use app().make(Config).set(…) to write. See configuration.

view(component, props?)

view<P>(component: (props: P, ...rest: any[]) => Renderable, props: P): Promise<string> view(component: (...rest: any[]) => Renderable): Promise<string>

Renders a component (with optional props) to a complete HTML document.

return view(Welcome, { appName: "Keel" });
return view(HomePage);

Notes: props are type-checked against the component's parameter. Resolves to a Promise<string> (a full HTML document, doctype included) — return it directly from a route handler. Sugar over app().make(View).render(component(props)). See views.

bind(token, factory)

bind<T>(token: Token<T>, factory: Factory<T>): void

Registers a transient binding — the factory runs on every make.

bind("clock", () => new Date());

Notes: the factory receives the container. Returns void (the underlying Container.bind returns this, but the helper does not). See container.

singleton(token, factory)

singleton<T>(token: Token<T>, factory: Factory<T>): void

Registers a shared binding — the factory runs once, then the value is cached.

singleton(Mailer, (app) => new Mailer(app.make(Config)));

Notes: the cached value lives for the life of the application. Returns void. See container.

instance(token, value)

instance<T>(token: Token<T>, value: T): T

Registers an already-constructed value as a shared instance, and returns it.

const version = instance("app.version", "0.30.0");

Notes: unlike bind/singleton, this returns the value you passed in, so you can register-and-use in one expression. See container.

make(token)

make<T>(token: Token<T>): T

Resolves a token out of the container.

const mailer = make(Mailer);
const version = make<string>("app.version");

Notes: a zero-arg class token resolves even without an explicit binding (the container builds it); an unbound string/symbol token throws Nothing bound in the container for […]. See container.

bound(token)

bound(token: Token): boolean

true if the token has a binding or a cached instance.

if (bound("clock")) make<Date>("clock");

Notes: a guard for optional services. Note a class token that make could auto-build still reports false here until it's explicitly bound. See container.

events()

events(): Events

Returns the application's event emitter singleton.

events().listenerCount("order.paid");
events().clear("order.paid");

Notes: use for once, off, listenerCount, and clear; for the common subscribe/emit pair prefer listen/emit below. See events.

emit(event, payload?)

emit<T = unknown>(event: string, payload?: T): Promise<void>

Emits an event, awaiting every listener in registration order.

await emit("user.registered", user);

Notes: shorthand for events().emit(…). The returned promise resolves once all listeners (including async ones) have run. No listeners → resolves immediately. See events.

listen(event, listener)

listen<T = unknown>(event: string, listener: Listener<T>): () => void

Subscribes to an event; returns an unsubscribe function.

const off = listen("user.registered", (user) => sendWelcome(user));
off(); // stop listening

Notes: shorthand for events().on(…). The listener may be sync or async. Call the returned function to remove it. See events.

cache()

cache(): Cache

Returns the application's cache singleton.

const stats = await cache().remember("stats", 60, () => computeStats());
await cache().put("otp", code, 300);

Notes: memory-backed per process/isolate by default; swap the store via a singleton(Cache, …) binding. See cache.

logger()

logger(): Logger

Returns the application's logger singleton.

logger().info("user registered", { userId: user.id });
logger().error("payment failed", { orderId });

Notes: structured JSON by default. logger().child({ … }) returns a logger with bound fields (e.g. a request id). See logger.

Interfaces & types

The helpers surface a few types from the services they front. You implement or pass these; you rarely construct them here.

Listener<T>

type Listener<T = unknown> = (payload: T) => void | Promise<void>

The shape of a function passed to listen. Sync or async; the payload type flows from listen<T>.

const onOrder: Listener<{ id: number }> = async (order) => fulfil(order.id);
listen("order.paid", onOrder);

Token<T> / Factory<T>

type Token<T = unknown> = string | symbol | Constructor<T> type Factory<T> = (app: Container) => T

The key and the factory used by bind/singleton/instance/make/bound. A token is a string, symbol, or class constructor; a factory receives the container so it can resolve its own dependencies. Documented in full under container.

Renderable

type Renderable = string | Promise<string> | { toString(): string | Promise<string> } | null | undefined

What a component passed to view() may return — a string, a JSX node, a promise of either, or nullish (renders empty). Documented in full under views.