Configuration
The configuration class is the main way to configure ARDOR: a @configuration() class whose @provide({ key }) methods return the options or the provider instances; start() binds each as a lazy SINGLETON.
import {
type BaseArdorApplication,
configuration,
CoreBindings,
DefaultRestDataProvider,
inject,
provide,
} from '@venizia/ardor';
@configuration()
export class ExampleConfiguration {
constructor(
@inject({ key: CoreBindings.APPLICATION_INSTANCE })
private readonly application: BaseArdorApplication,
) {}
@provide({ key: CoreBindings.REST_DATA_PROVIDER_OPTIONS })
restDataProviderOptions() {
return { url: '/api', noAuthPaths: ['/auth/sign-in'] };
}
@provide({ key: CoreBindings.DEFAULT_REST_DATA_PROVIDER })
restDataProvider() {
return this.application.instantiate(DefaultRestDataProvider).value(this.application);
}
}Each method decorated with @provide({ key }) defines a binding:
- Lazy resolution: The method is called on the first
application.get({ key }), never at startup. - Singleton scope: The binding defaults to
BindingScopes.SINGLETONunless an explicitscopeis passed in@provide({ key, scope }). The return value is cached on first use. - Options methods: Return plain JavaScript objects. For example,
restDataProviderOptions()returns anIRestDataProviderOptionsobject, andauthProviderOptions()returns anIAuthProviderOptionsobject. The post-login redirect path is configured atpaths.afterLogininside the auth provider options (AUTH_PROVIDER_OPTIONS), not as a top-level option. - Provider methods: Call
this.application.instantiate(ProviderClass).value(this.application)to create the helper instance with injected options and return the provider object for react-admin. For services such asDefaultAuthService, callingthis.application.instantiate(DefaultAuthService)returns the service instance directly.
NOTE
An application with no sign-in leaves DEFAULT_AUTH_PROVIDER unbound; ArdorApplication then runs without an auth provider.
To customize or replace a default provider, see Providers.
Options and provider bindings
The kernel defines eleven keys in CoreBindings (packages/kernel/src/common/keys.ts). An administrative application configures options and provider pairs using @provide({ key }) methods:
| Key | Value / Contract | Configured via | Reference |
|---|---|---|---|
CoreBindings.REST_DATA_PROVIDER_OPTIONS | Plain object (IRestDataProviderOptions) | @provide | REST data provider |
CoreBindings.DEFAULT_REST_DATA_PROVIDER | IDataProvider instance | @provide | REST data provider |
CoreBindings.AUTH_PROVIDER_OPTIONS | Plain object (IAuthProviderOptions) | @provide | Auth provider |
CoreBindings.DEFAULT_AUTH_SERVICE | DefaultAuthService instance | @provide | Auth provider |
CoreBindings.DEFAULT_AUTH_PROVIDER | IAuthProvider instance (unbound for public applications) | @provide | Auth provider |
CoreBindings.I18N_PROVIDER_OPTIONS | Plain object (II18nProviderOptions) | @provide | Internationalization |
CoreBindings.DEFAULT_I18N_PROVIDER | I18nProvider instance | @provide | Internationalization |
CoreBindings.APPLICATION_INSTANCE | AbstractArdorApplication container instance | preConfigure() | Application |
CoreBindings.APPLICATION_INFO | Plain object (IApplicationInfo) from getAppInfo() | preConfigure() | Application |
CoreBindings.FEATURES | Mounted feature array (Array<IFeatureBase>) | preConfigure() | Features |
CoreBindings.PERMISSION_PROVIDER | IPermissionProvider instance (optional) | @provide or bindContext() | Permission provider |
IDataProvider extends react-admin's nine data methods with exactly three more: send, getNetworkService, getDataSourceAuth.
The bind order
During application.start(), preConfigure() in packages/kernel/src/base/applications/abstract.ts executes in exact order:
this.bind({ key: CoreBindings.APPLICATION_INSTANCE }).toValue(this)- Binds the application container instance itself. Bound directly bypreConfigure().this.bind({ key: CoreBindings.APPLICATION_INFO }).toValue(this.getAppInfo())- Binds the metadata object returned bygetAppInfo(). Bound directly bypreConfigure().this.registerArtifacts()- Discovers all stereotyped classes (@configuration(),@service(),@repository(),@datasource(),@component()). Asserts that no two classes derive colliding binding keys. Binds each class under its derived key as a singleton. Binds each method decorated with@provide({ key })as a lazy provider (singleton by default).this.registerFeatures({ features: this.features() })- Validates that feature names are unique and that all configuration classes listed by features were discovered. Bindsthis.bind({ key: CoreBindings.FEATURES }).toValue(features). Bound directly bypreConfigure().this.bindingList()- Binds each class from the dictionary returned bythis.bindingList()under its literal key as a singleton.this.bindContext()- Calls the application subclass'sbindContext()method and returns its result.
Because preConfigure() runs least explicit first and bindContext() runs last, any binding declared inside bindContext() overrides a binding declared in a @configuration() class.
Other ways to bind
bindingList()
Override bindingList(): Record<string, TClass<unknown>> on the application class to bind undecorated or third-party classes under literal keys as singletons:
import {
BaseArdorApplication,
type IApplicationInfo,
} from '@venizia/ardor';
import { type TClass } from '@venizia/ignis-inversion';
class CatalogService {
list() {
return ['item-1', 'item-2'];
}
}
export class CustomBindingListApplication extends BaseArdorApplication {
getAppInfo(): IApplicationInfo {
return { name: 'shop', version: '1.0.0', description: 'Shop admin' };
}
override bindingList(): Record<string, TClass<unknown>> {
return {
'services.CatalogService': CatalogService,
};
}
bindContext() {}
}bindContext()
Implement bindContext() on the application class for imperative bindings or runtime overrides:
import {
BaseArdorApplication,
CoreBindings,
type IApplicationInfo,
} from '@venizia/ardor';
class InventoryService {
count() {
return 42;
}
}
export class CustomManualApplication extends BaseArdorApplication {
getAppInfo(): IApplicationInfo {
return { name: 'shop', version: '1.0.0', description: 'Shop admin' };
}
bindContext() {
this.service(InventoryService);
// Overrides REST_DATA_PROVIDER_OPTIONS provided by a @configuration() class
this.bind({ key: CoreBindings.REST_DATA_PROVIDER_OPTIONS }).toValue({
url: 'https://staging.api.example.com',
noAuthPaths: ['/auth/sign-in'],
});
}
}AbstractArdorApplication provides helper methods for registration:
this.service(Target)- binds underservices.Targetthis.repository(Target)- binds underrepositories.Targetthis.dataSource(Target)- binds underdatasources.Targetthis.component(Target)- binds undercomponents.Targetthis.bind({ key }).toValue(...)- binds an arbitrary value or instance directly
For domain classes, stereotype decorators (@service(), @repository(), @datasource(), @component()) register automatically during registerArtifacts(); see Dependency injection.
Multiple configuration classes and duplicate keys
An application can define multiple @configuration() classes to separate concerns:
import {
configuration,
CoreBindings,
provide,
} from '@venizia/ardor';
@configuration()
export class ApiConfiguration {
@provide({ key: CoreBindings.REST_DATA_PROVIDER_OPTIONS })
restDataProviderOptions() {
return { url: 'https://api.example.com', noAuthPaths: ['/auth/sign-in'] };
}
}
@configuration()
export class AuthConfiguration {
@provide({ key: CoreBindings.AUTH_PROVIDER_OPTIONS })
authProviderOptions() {
return { paths: { afterLogin: '/dashboard' } };
}
}- Configuration class collisions: Each configuration class is registered under a derived key in the
configurationsnamespace (such asconfigurations.ApiConfiguration).ArtifactBindingKeys.assertNoDerivedCollision()checks that no two classes derive the exact same binding key. These only collide when two classes share a name, which a minifier can cause. If two classes derive the same key,registerArtifacts()throws an error at startup. - Provided key collisions: If two configuration classes provide the same binding key, classes are registered in the order the registry discovered them, which is the order their modules were evaluated, and the last bind of a key wins. Binding a key again replaces the earlier binding. Do not rely on this order; no collision error is thrown for
@provide()keys. UsebindContext()for deterministic overrides.