Skip to content

The Axion host contract

axion.host is the global capability surface that @axion/core provides to every feature. It is the “host core” available to any JS feature running inside the host app: a small, fixed set of capabilities the feature calls into native for. Every member is JS feature → native host (native is the provider, JS is the consumer) — there are no streams and no state.

It is provided once at process start, globally — there is no per-feature scoping. Any feature that imports the hook gets the same surface.

Five members, all synchronous reads or fire-and-forget calls.

MemberKindParamsResultDescription
getCountryAndLanguageSync{ country, language }Host country/language config (in-memory read).
getLiteralSync{ key }stringResolve a localisation key from the native literals cache.
getAppVersionSyncstringHost app version (e.g. BuildConfig.VERSION_NAME).
trackEventVoid{ eventName, eventParams?: json }voidFire an analytics event. eventParams is optional free-form JSON.
openUrlVoid{ url }voidOpen a URL in the system browser / deep-link handler.

The definition uses the type-first marker style (Sync / Void); Sync(schema) and Sync(paramsSchema, resultSchema) produce a synchronous-query descriptor with the schema embedded. This is equivalent to the t.querySync / t.fire forms of the t.* DSL — either style yields the same runtime descriptor.

packages/core/src/host/HostContract.ts
export const useHost = defineContract('axion.host', {
methods: {
getCountryAndLanguage: Sync(t.object({ country: t.string(), language: t.string() })),
getLiteral: Sync(t.object({ key: t.string() }), t.string()),
getAppVersion: Sync(t.string()),
trackEvent: Void(t.object({ eventName: t.string(), eventParams: t.optional(t.json()) })),
openUrl: Void(t.object({ url: t.string() })),
},
});

useHost is the raw contract hook, re-exported from @axion/core. Because the contract is process-global there is nothing to scope — call it and read the members directly:

import { useHost } from '@axion/core';
const { getLiteral, trackEvent } = useHost();
const label = getLiteral({ key: 'some.key' }); // Sync
trackEvent({ eventName: 'view_item' }); // Void, fire-and-forget

@axion/core also ships ergonomic wrapper hooks that add fallbacks and small conveniences over the raw members. Each is re-exported from @axion/core.

// useCountryAndLanguage
const { getCountryAndLanguage, getLanguageCode, getIntlLanguageCode } = useCountryAndLanguage();
getLanguageCode(); // "de_DE"
getIntlLanguageCode(); // "de-de"
// useLiterals — try getLiteral; on empty/throw → defaultValue ?? key
const { t } = useLiterals();
t('some.key', 'fallback');
// useAppVersion — '' when the host returns null
const { getAppVersion } = useAppVersion();
// useTrackEvent
const { trackEvent } = useTrackEvent();
trackEvent('view_item', { id: 42 });
// useOpenUrl — open(url) → openUrl({ url })
const { open } = useOpenUrl();
open('https://example.com');

The generator emits a provider type per platform: a Kotlin interface and a Swift protocol. The host app implements it; the concrete implementations live in the host apps (Lidl Plus / RN.LiA / the platform apps), not in the BridgeKit framework.

interface AxionHost {
fun getCountryAndLanguage(): GetCountryAndLanguageResult
fun getLiteral(params: GetLiteralParams): String
fun getAppVersion(): String
fun trackEvent(params: TrackEventParams)
fun openUrl(params: OpenUrlParams)
}

The three Sync members run on the synchronous path; trackEvent and openUrl run on the async/fire path (they return nothing). The Swift protocol is shipped through the AxionContracts module inside Axion.xcframework (see Installation).

The contract hash is 8e552bd7 — a stable hash of the full descriptor (id plus every member kind and its param/result schemas). It appears in the generated Kotlin, the generated Swift, and bridgekit.lock. Per-member hashes give fine-grained drift attribution:

MemberHash
getCountryAndLanguage27aee930
getLiteral494175a7
getAppVersion71ff3fa0
trackEvent99cf4370
openUrlfb545690

If the TypeScript contract changes — a member added or a schema altered — the hash changes, bridgekit.lock drifts, and axion bridgekit generate --check fails until the bindings are regenerated and committed. See Drift detection.

The host surface is deliberately small. It does not include:

  • Auth / session / identity — no user, token, or login state.
  • Navigation beyond openUrl — no push/pop, no screen registry. openUrl covers external URLs and deep links only.
  • Feature flags / remote config.
  • Push notifications.
  • Storage of any kind, including secure storage.
  • Device info beyond version — no OS version, model, or connectivity.
  • Streams or state — every member is a synchronous read or a fire-and-forget call.

eventParams on trackEvent is intentionally untyped (t.optional(t.json())). The host is a global capability surface by design, not a place to add feature-specific behavior — feature capabilities belong in feature-owned contracts (for example Connect’s connect.host; see the Connect migration guide).