Binding keys
CoreBindings is a class of string constants. Each constant is a key in the IGNIS container that an ARDOR application extends. LocalStorageKeys is the same idea for browser storage. This page lists every key, says who is responsible for binding it, and shows how the key union that useInjectable accepts is built and extended.
Prerequisites
You have an application class that extends AbstractArdorApplication or BaseArdorApplication (see Application).
Quick Reference
| Export | Kind | Package | Role |
|---|---|---|---|
CoreBindings | class | @venizia/ardor | Container keys for the application, providers, services and their options |
LocalStorageKeys | class | @venizia/ardor | Keys for auth state in localStorage |
AbstractArdorApplication.injectable | method | @venizia/ardor | Binds a class under ${scope}.${ClassName} as a singleton |
AbstractArdorApplication.service | method | @venizia/ardor | injectable('services', value) |
useInjectable | const | @venizia/ardor | Resolves a binding by key or by class target |
TUseInjectableKeysDefault | type | @venizia/ardor | Union of every CoreBindings string value |
IUseInjectableKeysOverrides | interface | @venizia/ardor-react | Empty interface you augment to add keys |
TUseInjectableKeys | type | @venizia/ardor | TUseInjectableKeysDefault | keyof IUseInjectableKeysOverrides |
TUseInjectableOptions | type | @venizia/ardor | Options union accepted by useInjectable |
CoreBindings
All keys and their string values, exactly as defined in the kernel.
| Constant | Value | Bound by |
|---|---|---|
APPLICATION_INSTANCE | @app/application/instance | Framework (preConfigure) |
APPLICATION_INFO | @app/application/info | Framework (preConfigure) |
DEFAULT_AUTH_PROVIDER | @app/application/auth/default | bindContext() |
DEFAULT_I18N_PROVIDER | @app/application/i18n/default | bindContext() |
DEFAULT_REST_DATA_PROVIDER | @app/application/data/rest/default | bindContext() |
DEFAULT_AUTH_SERVICE | @app/application/service/auth/default | bindContext() |
AUTH_PROVIDER_OPTIONS | @app/application/options/auth | bindContext() |
REST_DATA_PROVIDER_OPTIONS | @app/application/options/rest/data | bindContext() |
I18N_PROVIDER_OPTIONS | @app/application/options/i18n | bindContext() |
The three *_OPTIONS keys hold the options objects read by the matching provider: IAuthProviderOptions, IRestDataProviderOptions and II18nProviderOptions. The provider keys hold the provider instances. See Data provider, Auth provider and i18n for what each provider expects.
import { CoreBindings } from '@venizia/ardor';
const key: string = CoreBindings.DEFAULT_REST_DATA_PROVIDER;
// '@app/application/data/rest/default'What the framework binds itself
AbstractArdorApplication.start() runs preConfigure() and then postConfigure(). preConfigure() binds exactly two keys before it calls your bindContext():
export abstract class AbstractArdorApplication {
...
preConfigure(): ValueOrPromise<void> {
this.bind({ key: CoreBindings.APPLICATION_INSTANCE }).toValue(this);
this.bind({ key: CoreBindings.APPLICATION_INFO }).toValue(this.getAppInfo());
return this.bindContext();
}
...
}APPLICATION_INSTANCEis the application object itself.APPLICATION_INFOis whatevergetAppInfo()returns, bound as a value.getAppInfo()is typedValueOrPromise<IApplicationInfo>, so if you return a promise, the promise is what gets bound.
postConfigure() is a no-op in the abstract class. Nothing else is bound by the kernel. Every other CoreBindings key is the job of bindContext() - either yours or the one in the subclass you extend. Check Application for what ArdorApplication from @venizia/ardor-admin binds for you.
What the application binds
bindContext() is abstract. It runs after the two framework keys are in place, so it can read them. Bind options with toValue, and bind providers or services with toClass or service().
import { BaseArdorApplication, CoreBindings, type IRestDataProviderOptions } from '@venizia/ardor';
export class MyApplication extends BaseArdorApplication {
bindContext() {
const options: IRestDataProviderOptions = { ... };
this.bind({ key: CoreBindings.REST_DATA_PROVIDER_OPTIONS }).toValue(options);
...
}
getAppInfo() {
return { ... };
}
}The services.* namespace
injectable() binds a class under a key derived from a scope and the class name. service() fixes the scope to services.
export abstract class AbstractArdorApplication {
...
injectable<T>(scope: string, value: TClass<T>, tags?: Array<string>): void;
service<T>(value: TClass<T>): void;
}Both are positional - this is the IGNIS container surface, not an ARDOR options object. The resulting key is ${scope}.${value.name}, so this.service(UserService) binds services.UserService. The binding is toClass(value) with scope BindingScopes.SINGLETON: one instance per application. Tags are applied when given.
export class MyApplication extends BaseArdorApplication {
bindContext() {
this.service(UserService); // key: 'services.UserService'
this.injectable('helpers', ClockHelper); // key: 'helpers.ClockHelper'
this.injectable('services', AuditService, ['audit']); // tagged
}
...
}Nothing in CoreBindings covers services.* keys. To pass one to useInjectable({ key }) with type checking, augment the key union as shown below, or resolve by target instead.
LocalStorageKeys
Three constants for auth state in localStorage. Their names say what each slot holds.
| Constant | Value |
|---|---|
KEY_AUTH_TOKEN | @app/auth/token |
KEY_AUTH_IDENTITY | @app/auth/identity |
KEY_AUTH_PERMISSION | @app/auth/permission |
import { LocalStorageKeys } from '@venizia/ardor';
const raw = localStorage.getItem(LocalStorageKeys.KEY_AUTH_TOKEN);Use the constants rather than the raw strings so a rename in the framework does not leave stale entries behind in your code. How the auth service reads and writes these slots is covered in Auth provider.
How useInjectable keys are typed
The key parameter of useInjectable is not string. It is a union built in @venizia/ardor-react:
export interface IUseInjectableKeysOverrides {}
export type TUseInjectableKeysDefault = Extract<ValueOf<typeof CoreBindings>, string>;
export type TUseInjectableKeys = TUseInjectableKeysDefault | keyof IUseInjectableKeysOverrides;TUseInjectableKeysDefaultis every string value onCoreBindings- the nine keys in the table above.IUseInjectableKeysOverridesis empty. Its property names, not its property types, are added to the union.TUseInjectableKeysis the union the hook accepts.
To add your own keys, augment the interface once in a .d.ts or any module file. The property type is irrelevant because only keyof is used.
import { useInjectable } from '@venizia/ardor';
declare module '@venizia/ardor-react' {
interface IUseInjectableKeysOverrides {
'services.UserService': unknown;
}
}
class UserService {
list() {
return [] as Array<string>;
}
}
export const useUserService = () => {
return useInjectable<UserService>({ key: 'services.UserService' });
};The augmentation targets @venizia/ardor-react, where the interface is declared, even if you import from @venizia/ardor. See Module augmentation for the file layout that keeps this in one place.
Resolving by key or by target
useInjectable takes one options object. key and target are mutually exclusive; container is optional in both forms.
export type TUseInjectableOptions =
| { container?: Container; key: TUseInjectableKeys; target?: never }
| { container?: Container; key?: never; target: TClass<AnyType> };
export const useInjectable: <T>(opts: TUseInjectableOptions) => T;Resolution order:
- Use
opts.containerif given, otherwiseApplicationContext.container. If neither exists, throw[useInjectable] Failed to determine injectable container!. - If
keyis set, returncontainer.get<T>({ key }). - Otherwise look up the class in the container's metadata registry with
getBindingKey({ target }). If nothing is found, throw with the message:Decorate it (@service, @component, ...) or register it on the application before injecting. - Return
container.get<T>({ key: resolved }).
By key, with a CoreBindings constant:
import { CoreBindings, useInjectable, type IDataProvider } from '@venizia/ardor';
export const useDataProvider = () => {
return useInjectable<IDataProvider>({
key: CoreBindings.DEFAULT_REST_DATA_PROVIDER,
});
};By target, with no key augmentation needed:
import { useInjectable } from '@venizia/ardor';
class ReportService {
build() {
return 'report';
}
}
export const useReportService = () => {
return useInjectable<ReportService>({ target: ReportService });
};Against an explicit container, bypassing ApplicationContext:
import { CoreBindings, useInjectable, type IApplicationInfo } from '@venizia/ardor';
export const useAppInfoFrom = () => {
return useInjectable<IApplicationInfo>({
container,
key: CoreBindings.APPLICATION_INFO,
});
};useInjectable does not check that T matches what is bound. The generic is an assertion, not a lookup.
Common pitfalls
- Binding a provider key before
preConfigure()has run.bindContext()is the right place. It is called frompreConfigure()afterAPPLICATION_INSTANCEandAPPLICATION_INFOexist. Binding in the constructor happens before either of them. - Returning a promise from
getAppInfo()and expecting an object underAPPLICATION_INFO. The value is bound as-is withtoValue, so consumers get the promise. - Passing
'services.UserService'askeywithout augmentation. It is not inTUseInjectableKeysDefault, so TypeScript rejects it. Either augmentIUseInjectableKeysOverridesor usetarget. - Relying on
value.nameunder minification.service()builds the key from the class's runtimename. If your bundler renames classes, the key in production differs from the one you typed in an augmentation. Resolve bytargetor disable class name mangling. - Augmenting
@venizia/ardorinstead of@venizia/ardor-react. The interface lives in@venizia/ardor-react. Augmenting the umbrella package does not extend the union. targetfor a class that is only bound by hand elsewhere. Step 3 above reads the metadata registry. If the lookup returns nothing, the hook throws. The error text lists the accepted ways to register.- Both
keyandtargetset. The options type forbids it (target?: never/key?: never), and at runtimekeywins.
Related
- Application -
start(),preConfigure(),bindContext()and whatArdorApplicationbinds - Hooks - the full
useInjectableanduseApplicationContextreference - Data provider - what sits behind
DEFAULT_REST_DATA_PROVIDERandREST_DATA_PROVIDER_OPTIONS - Auth provider -
DEFAULT_AUTH_PROVIDER,DEFAULT_AUTH_SERVICEand theLocalStorageKeysslots - i18n -
DEFAULT_I18N_PROVIDERandI18N_PROVIDER_OPTIONS - Types -
ValueOf,ValueOrPromiseand the other helpers used in these signatures - Module augmentation - where to put
IUseInjectableKeysOverridesaugmentations - Migrating from ra-core-infra - old
container.bind({ key, value })calls versusbind({ key }).toValue()