The Framework Access Pattern
KOS uses decorators as the primary interface to framework services – similar to Spring’s annotation-driven approach. Rather than manually configuring services, KOS developers use declarative decorators to access logging, configuration, event handling, async operations, and more.
Core Decorators
@kosModel – Foundation
The foundation decorator that establishes model identity and lifecycle. Every KOS model must use this decorator to register with the framework and enable dependency injection.
@kosModel({ modelTypeId: "device-controller", singleton: false })
export class DeviceControllerImpl {
// Model foundation - always required
}@kosLoggerAware – Structured Logging
Automatically injects a contextual logger instance into the model. The logger is pre-configured with the model’s type for structured logging across the application.
@kosModel({ modelTypeId: "device-controller", singleton: false })
@kosLoggerAware()
export class DeviceControllerImpl {
// this.logger automatically injected
init(): void {
this.logger.info("Device controller initialized");
}
}@kosDependency – Model Dependencies
Property decorator that automatically injects other KOS models as dependencies. The framework handles instantiation, lifecycle coordination, and cleanup based on the specified lifecycle policy.
@kosModel({ modelTypeId: "device-controller", singleton: false })
export class DeviceControllerImpl {
@kosDependency({
modelType: "calibration-service-model",
lifecycle: DependencyLifecycle.READY
})
calibrationService?: CalibrationServiceModel; // Auto-injected
}Resolution Policy Options
Control how the framework handles dependency resolution failures using the resolutionPolicy parameter:
@kosModel({ modelTypeId: "device-controller", singleton: false })
export class DeviceControllerImpl {
// CREATE (default) - Attempts to create instance, fails if options missing
@kosDependency({
modelType: "network-interface",
lifecycle: DependencyLifecycle.INIT
})
network?: NetworkInterfaceData;
// FAIL - Throws exception with detailed logging if resolution fails
@kosDependency({
modelType: "critical-service",
resolutionPolicy: DependencyResolutionPolicy.FAIL
})
criticalService?: CriticalServiceData;
// CONTINUE - Logs error but allows execution with undefined dependency
@kosDependency({
modelType: "optional-feature",
resolutionPolicy: DependencyResolutionPolicy.CONTINUE
})
optionalFeature?: OptionalFeatureData;
}Policy Behaviors:
All resolution policies follow a lookup-first approach: The framework first checks if a model with the specified ID already exists. If found and the type matches, it returns the existing instance. Only if no existing model is found does the framework apply the resolution policy.
- CREATE (default): Looks up existing model first. If not found, attempts to create a new model instance. If the model requires options (via
optionsRequiredflag) and none are provided, throws an exception with detailed error information. - FAIL: Looks up existing model first. If not found, explicitly throws an exception with comprehensive diagnostic logging. Use for critical dependencies where failure should halt execution.
- CONTINUE: Looks up existing model first. If not found, logs detailed error information but allows the model to continue initialization with an undefined dependency. Use for optional features that can degrade gracefully.
Enhanced Error Logging
When dependency resolution fails, the framework logs comprehensive diagnostic information:
Failed to resolve dependency for model device-controller-123:
- Dependency Type: network-interface
- Dependency ID: eth0
- Resolution Policy: FAIL
- Parent Model: DeviceController
- Parent Model ID: device-controller-123
- Provided Options: {
"interface": "eth0"
}
- Error: No model registered for type network-interfaceThis detailed logging helps quickly identify:
- Which model failed to resolve its dependency
- What dependency was requested
- What resolution policy was in effect
- What options were provided
- The specific error that occurred
@kosTopicHandler – Event Handling
Method decorator that automatically subscribes to WebSocket topics and routes messages to the decorated method. Subscriptions are managed automatically with proper cleanup on model destruction.
@kosModel({ modelTypeId: "device-controller", singleton: false })
export class DeviceControllerImpl {
@kosTopicHandler({ topic: "device.status.changed" })
handleDeviceStatus(payload: DeviceStatusPayload): void {
// Handle WebSocket messages automatically
}
}@kosConfigProperty – Configuration
Property decorator that creates reactive connections to the KOS configuration system. Configuration values are automatically synchronized and changes propagate reactively to the UI.
@kosModel({ modelTypeId: "device-controller", singleton: false })
export class DeviceControllerImpl {
@kosConfigProperty({ path: "device.config", attribute: "maxTemperature" })
maxTemp!: KosConfigProperty<number>;
checkTemperature(): void {
if (this.maxTemp.value > 80) {
// Configuration value automatically synced
}
}
}@kosStateProp – Device State Reflection
Property decorator that reflects external device state into KOS models. Creates reactive connections to device properties without local persistence – values come directly from hardware systems.
@kosModel({ modelTypeId: "door-controller", singleton: false })
export class DoorControllerImpl {
@kosStateProp({ path: DOOR_PATH, attribute: "state" })
private doorState!: KosStateProp<boolean>;
get isOpen(): boolean {
return this.doorState.value ?? false; // Reflects actual door state
}
}@kosFutureAware + @kosFuture – Async Operations
Class decorator (@kosFutureAware) adds progress tracking properties, while method decorator (@kosFuture) marks async methods as Future operations. The framework automatically manages progress, status, and cancellation.
interface CalibrationModelImpl extends KosFutureAware<CalibrationResult> {}
@kosModel({ modelTypeId: "calibration-model", singleton: false })
@kosFutureAware()
export class CalibrationModelImpl {
// this.progress, this.status, this.isRunning automatically available
@kosFuture()
async startCalibration(): Promise<CalibrationResult> {
// Method becomes Future operation - framework handles progress/status
return await this.calibrationService.calibrate();
}
}@kosContainerAware – Collection Management
Automatically injects container management capabilities for handling collections of models. Provides methods for adding, removing, and querying models with reactive updates and optional indexing.
interface DeviceContainerImpl extends KosContainerAware<DeviceModel> {}
@kosModel({ modelTypeId: "device-container", singleton: false })
@kosContainerAware<DeviceModel>()
export class DeviceContainerImpl {
// this.getModel(), this.addModel(), this.removeModel() automatically available
// this.models, this.data automatically available
}@kosModelEffect – Reactive Side Effects
Method decorator that creates reactive side effects similar to React’s useEffect. Effects automatically execute when specified dependencies change, with optional cleanup function support.
@kosModel({ modelTypeId: "temperature-monitor", singleton: false })
export class TemperatureMonitorImpl {
@kosStateProp({ path: SENSOR_PATH, attribute: "temperature" })
private temperature!: KosStateProp<number>;
@kosModelEffect({
dependencies: (model) => [model.temperature.value]
})
checkOverheating(): void {
if (this.temperature.value > 80) {
this.logger.warn("Overheating detected!");
}
}
}@kosChild – Model Hierarchy
Property decorator that establishes parent-child relationships between models. Child models are automatically managed through their parent’s lifecycle with coordinated initialization and cleanup.
@kosModel({ modelTypeId: "system-controller", singleton: true })
export class SystemControllerImpl {
@kosChild deviceController?: DeviceControllerModel; // Auto lifecycle management
}@kosStateMachine – Finite State Machines
Class decorator that adds declarative state machine capabilities to models. Manages application-specific business logic states with automatic validation, transitions, and observable state tracking. See Finite State Machines for complete documentation.
enum PourState {
IDLE = "IDLE",
POURING = "POURING",
}
type PourEvent = "START_POUR" | "POUR_COMPLETED";
interface PourModelImpl extends KosStateMachineAware<PourState, PourEvent> {}
@kosModel({ modelTypeId: "pour-model", singleton: false })
@kosStateMachine<PourState, PourEvent>(
{
initial: PourState.IDLE,
states: {
[PourState.IDLE]: { on: { START_POUR: PourState.POURING } },
[PourState.POURING]: { on: { POUR_COMPLETED: PourState.IDLE } },
},
},
{ throwOnInvalid: false }
)
export class PourModelImpl {
// this.currentState, this.transition(), this.canTransition() automatically available
}@kosStateEntry / @kosStateExit – State Lifecycle
Method decorators that execute functions when entering or exiting specific FSM states. Entry handlers run after state transition, exit handlers run before transition.
@kosModel({ modelTypeId: "pour-model", singleton: false })
@kosStateMachine<PourState, PourEvent>(/* config */)
export class PourModelImpl {
@kosStateEntry(PourState.POURING)
handlePouringStart(): void {
this.logger.info('Pour started');
EventBus.publish(TOPIC_POUR_STARTED, { id: this.id });
}
@kosStateExit(PourState.POURING)
handlePouringEnd(): void {
this.logger.info('Pour ended');
}
}@kosStateGuard – State Validation
Method decorator that validates FSM state before executing a method. When validation fails, either throws an error or logs a warning depending on throwOnInvalid configuration.
@kosModel({ modelTypeId: "pour-model", singleton: false })
@kosStateMachine<PourState, PourEvent>(/* config */)
export class PourModelImpl {
@kosStateGuard({ allowedStates: [PourState.IDLE] })
public async startPour(): Promise<void> {
// Only executes if currentState === IDLE
// Otherwise: logs warning and returns undefined (if throwOnInvalid: false)
this.transition("START_POUR");
}
}@kosCompanion – Parent Model Proxying
Class decorator that creates companion models by transparently proxying properties and methods from a parent model. Enables model extension without modifying the original model implementation.
Supports lifecycle parameter to defer companion creation until parent’s lifecycle methods complete, allowing factory functions to access data loaded during parent initialization.
interface BrandCompanionImpl extends KosCompanionDecorator<AvailabilityModel> {}
@kosModel({ modelTypeId: "brand-companion", singleton: false })
@kosCompanion() // Transparent property proxying from parent - created during INIT
export class BrandCompanionImpl {
// Parent properties/methods automatically available
get brandInfo(): string {
return `Brand: ${this.name} - Available: ${this.isAvailable}`; // From parent
}
}
// Lifecycle-aware companion - waits for parent's load() to complete
interface CarbAgitatorImpl extends KosCompanionComposition<AssemblyModel> {}
@kosModel({ modelTypeId: "carb-agitator-model", singleton: false })
@kosCompanion({
mode: "composition",
lifecycle: DependencyLifecycle.LOAD // Created after parent.load() completes
})
export class CarbAgitatorImpl {
get holder() {
// Safe to access data loaded during parent's load() method
const parent = this.getCompanionParent();
return parent.getHoldersByGroup("carbTank")[0];
}
}Lifecycle Options:
DependencyLifecycle.INIT– Default, created immediatelyDependencyLifecycle.LOAD– After parent’s load() completesDependencyLifecycle.READY– After parent’s ready() completesDependencyLifecycle.ACTIVATE– After parent’s activate() completesDependencyLifecycle.ONLINE– After parent’s online() completes
HTTP Decorators – REST Endpoints
Method decorators (@get, @post, @put, @delete) that expose model methods as HTTP endpoints. Automatically registers routes with the browser router and handles request/response lifecycle.
@kosModel({ modelTypeId: "api-service", singleton: false })
export class ApiServiceImpl {
@get("/api/devices/:id")
async getDevice(req: KosRequest, res: KosResponse): Promise<void> {
const device = await this.findDevice(req.params.id);
res.status(200).send(device);
}
@post("/api/devices")
async createDevice(req: KosRequest, res: KosResponse): Promise<void> {
const device = await this.createDevice(req.body);
res.status(201).send(device);
}
}Decorator Reference Guide
Class Decorators:
@kosModel(modelTypeId)– Foundation decorator, always required@kosLoggerAware()– Injects structured logging@kosFutureAware()– Adds async operation management properties@kosContainerAware<T>()– Injects container management capabilities@kosCompanion(options)– Creates companion model relationships@kosStateMachine<TState, TEvent>(config, options)– Adds finite state machine capabilities
Property Decorators:
@kosDependency(options)– Declares model dependencies@kosConfigProperty(params)– Connects properties to configuration system@kosStateProp(params)– Reflects device state properties@kosChild– Marks child models for lifecycle management
Method Decorators:
@kosTopicHandler(options)– Handles WebSocket topic messages@kosFuture(props?)– Marks methods as Future operations@kosModelEffect(options)– Creates reactive side effects@kosStateEntry<TState>(state)– Executes when entering FSM state@kosStateExit<TState>(state)– Executes when exiting FSM state@kosStateGuard<TState>(options)– Validates FSM state before method execution@get(path),@post(path),@put(path),@delete(path)– HTTP endpoint handlers
Complete Example
interface DeviceManagerImpl extends KosContainerAware<DeviceModel>, KosFutureAware<CalibrationResult>, KosLoggerAware {}
@kosModel({ modelTypeId: "device-manager", singleton: false })
@kosLoggerAware()
@kosContainerAware<DeviceModel>()
@kosFutureAware()
export class DeviceManagerImpl {
// Critical dependency - must exist or initialization fails
@kosDependency({
modelType: "calibration-service-model",
lifecycle: DependencyLifecycle.READY,
resolutionPolicy: DependencyResolutionPolicy.FAIL
})
calibrationService?: CalibrationServiceModel;
// Optional telemetry - graceful degradation if unavailable
@kosDependency({
modelType: "telemetry-service",
lifecycle: DependencyLifecycle.READY,
resolutionPolicy: DependencyResolutionPolicy.CONTINUE
})
telemetryService?: TelemetryServiceModel;
@kosConfigProperty({ path: "calibration.config", attribute: "settings" })
calibrationSettings!: KosConfigProperty<CalibrationConfig>;
@kosTopicHandler({ topic: "device.calibration.requested" })
handleCalibrationRequest(payload: CalibrationRequestPayload): void {
this.logger.info(`Calibration requested for device: ${payload.deviceId}`);
// Optional telemetry - safely check before use
if (this.telemetryService) {
this.telemetryService.trackEvent("calibration_requested");
}
this.startCalibration(payload.deviceId);
}
@kosFuture()
async startCalibration(deviceId: string): Promise<CalibrationResult> {
const device = this.getModel(deviceId); // From @kosContainerAware
if (!device) throw new Error(`Device not found: ${deviceId}`);
return await this.calibrationService.calibrateDevice(deviceId);
}
@get("/api/devices")
async getDevices(req: KosRequest, res: KosResponse): Promise<void> {
res.status(200).send(this.data); // From @kosContainerAware
}
}Key Benefits
- Eliminates Boilerplate: Declarative service access vs manual configuration
- Consistent Patterns: Same approach across all framework services
- Type Safety: TypeScript interfaces provide compile-time validation
- Automatic Lifecycle: Framework handles service initialization and cleanup
- Discoverable: IDE auto-completion reveals available decorators and options
The KOS decorator pattern transforms framework interaction from imperative configuration to declarative capability definition, making applications both powerful and maintainable.
