Skip to Content
HeronJS v4 is now available.
v4Resource Hooks

Global hooks and resource ownership

HeronJS.create installs hooks and noun acquisition functions on globalThis. Applications use them directly; neither installGlobalHooks() nor app.run() is required. The useX hooks remain available, and the noun functions share their implementations and inferred types.

import {HeronJS} from '@heronjs/core'; import {MongoDataSource, MongoSource} from '@heronjs/core/mongodb'; import {Module} from '@heronjs/common'; @MongoSource({name: 'documents', uri: 'mongodb://localhost:27017', database: 'documents', profile: 'main', revision: '1'}) class Documents extends MongoDataSource {} @Module({databases: [Documents]}) class AppModule {} await using app = await HeronJS.create({module: AppModule, runtime: {transport: 'stdio'}}); using a = datasource(Documents); await using b = datasource(Documents); await using connection = await mongo(a.value); await connection.value.database.collection('events').insertOne({kind: 'started'});

datasource, cql and http are callable APIs, independent of the variable name. A synchronous borrowed lease supports both using a = datasource(Source) and await using a = datasource(Source). Native SQL/MongoDB/CQL acquisition is asynchronous: await using a = await cql(Source). The first await waits for disposal; the initializer’s await waits for acquisition. Read the acquired resource through .value.

Datasource and tenant selection

CallSelection
datasource()Default registered datasource
datasource(Source)Class/typed token, with inferred instance type
datasource('documents')Datasource registered as documents, when that name exists
datasource('tenant-a')Default datasource scoped to tenant-a, when no datasource has that name
datasource('documents', 'tenant-a')Named datasource scoped to tenant-a
datasource(undefined, 'tenant-a')Default datasource scoped to tenant-a, even if that tenant matches a datasource name
datasource(Source, 'tenant-a')Typed tenant view of the selected source

An omitted or unregistered datasource name falls back to the default source. With two arguments, the second argument is the tenant ID. With a single unregistered string, that string is the tenant ID. A sole registered datasource is the default; applications with multiple datasources must designate a default to use default lookup. Missing defaults fail with RESOURCE_UNAVAILABLE.

Tenant views share their owner’s managed pools and do not mutate the shared source. Their repositories, migrations and native client hooks use the selected tenant. MongoDB and CQL resolve tenant connection settings through resolveTenant; SQL uses the selected tenant database. Releasing a view does not close its application-owned datasource. A tenant view has the backend’s framework type, rather than promising subclass-specific behavior.

using selected = datasource(Documents, 'tenant-a'); await using connection = await mongo(selected.value); // Equivalent native acquisition, with no intermediate source handle: await using named = await mongo('documents', 'tenant-a');

sql, mongo, cql and their useX hooks accept a datasource instance, a typed class/token, a registered name or no selector, plus an optional tenant. They verify that the selected source belongs to the requested backend. String/default source lookup returns unknown because an untyped name cannot establish a compile-time resource type; class/token lookup preserves inference. No caller-selected generic can invent a type for a string.

Globals and application context

Core/common typings include the common globals automatically. Loading a feature entrypoint registers its hook definitions and includes its global typings: /sql, /mongodb, /cql, /cache, or the corresponding protocol package. Definitions do not acquire engines or load optional drivers. Loaded features become global at application startup; a feature loaded later joins the active installation. Package imports alone do not install globals.

Globals resolve the current async application scope. Outside a scoped operation, the sole running application is the default. When multiple applications are running, an unscoped call fails with AMBIGUOUS_CONTEXT; it never picks the last-created application. HTTP callbacks establish their owning context automatically. Bootstrap and background work started there inherit their application scope. app.run(callback) remains available to explicitly select an application for external callbacks in multi-application processes.

Installation checks conflicting names before mutation, reference-counts application ownership, and removes only Heron-owned globals after the final application closes. Startup failure releases its installation. Imported functions continue to be available after globals are removed, but resource lookup without a running application fails with NO_CONTEXT. Access through a closed application scope fails with CONTEXT_CLOSED.

installGlobalHooks() remains available for explicit integration/test scopes. The legacy common/globals import path remains accepted; the normal common and feature entrypoints now provide ambient declarations. Ordinary applications need neither legacy setup step.

Acquisition functionRetained hookResult and disposal
container()useContainer()Borrowed container lookup; never closes the container
datasource(Source)useDataSource(Source)Borrowed framework resource
http()useHttp()Borrowed HTTP engine; never closes its listener
rpc()useRpc()Borrowed router/plugin access
await sql(Source)await useSql(Source)Query builder lease; releases acquisition tracking
await mongo(Source)await useMongo(Source)MongoDB client/database lease
await cql(Source)await useCql(Source)CQL client/keyspace lease
cache()useCache()Borrowed cache engine
mcp(context) / acp(context)useMcp(context) / useAcp(context)Borrowed active protocol context
messaging(context)useMessaging(context)Borrowed broker delivery context

Core hook implementations live in src/hooks/. Public exports live in src/cache/index.ts and src/datasources/<backend>/index.ts. Separate entrypoints preserve optional-driver isolation. Noun and hook names are direct exports from the same implementation, with no second engine, duplicated pool or forwarding module.

Disposal

Access through a disposed lease fails with RESOURCE_RELEASED. Cleanup is idempotent, including failed asynchronous cleanup. Borrowed disposal does not call the native instance’s close, destroy or disconnect. Finish cursors, streams, subscriptions and transactions before releasing their client lease. A previously copied native reference cannot be revoked by the lease.

When returning asynchronous work from an await using scope, use return await work() so disposal waits for that work. Standard JavaScript disposal preserves both body and cleanup failures as SuppressedError. await using app = await HeronJS.create(...) owns application shutdown; resource leases only own their acquisition.

Based on the 4.1.0 development source .

Last updated on