are_engine_core 1.0.2
are_engine_core: ^1.0.2 copied to clipboard
Action Rule Event Engine - Zero dependency, cross-platform, lightweight event-rule-action engine for Dart & Flutter.
are_engine_core — Action Rule Event Engine #
Zero dependency, lightweight event-rule-action engine for Dart & Flutter.
This package is the Dart port of the C# ARE.Core engine. Same architecture, same API, same behavior.
Installation #
dependencies:
are_engine_core: ^1.0.2
dart pub get
Quick Start #
import 'package:are_engine_core/are_engine_core.dart';
void main() async {
final engine = AreEngine();
// Register an action
engine.registerInlineAction('send_email', (ctx, s) async {
print('Email sent: ${s.get<String>('template')}');
});
// Define a rule
engine.addRule(
Rule.create('vip_order')
.on('order.created')
.whenGreaterThan('total', 5000.0)
.then('send_email', (s) => s.set('template', 'vip_welcome')),
);
// Fire an event
await engine.fire('order.created', (e) => e.set('total', 7500.0));
// Output: Email sent: vip_welcome
}
Detailed Usage #
Defining an Action — Class-Based #
class DamageAction implements IAction {
@override
String get actionType => 'damage';
@override
Future<void> execute(AreContext context, ActionSettings settings) async {
final amount = settings.get<int>('amount') ?? 0;
final target = context.get<String>('target') ?? 'player';
print('$target took $amount damage!');
context.set('lastDamage', amount);
}
}
// Registration
engine.registerAction(DamageAction());
Defining an Action — Inline (Quick Prototyping) #
engine.registerInlineAction('log', (ctx, s) async {
print(s.get<String>('message'));
});
Defining a Rule — Fluent Builder #
engine.addRule(
Rule.create('boss_room_spawn')
.inGroup('spawning')
.withPriority(10)
.on('player.enter_zone')
.withMatchMode(MatchMode.all)
.whenEquals('zone_type', 'boss')
.when('level_check', (evt, ctx) {
final level = evt.data['player_level'] ?? 0;
return level >= 5;
})
.then('spawn_enemy', (s) => s.set('type', 'dragon').set('count', 1), 0)
.then('play_sound', (s) => s.set('clip', 'boss_roar'), 1),
);
Listening to Multiple Events #
Rule.create('license_warning')
.on('app.started', 'license.checked')
.when('expiring', (evt, ctx) {
final days = evt.data['days_remaining'] ?? 999;
return days <= 7;
})
.then('show_notification', (s) => s
.set('title', 'License Warning')
.set('message', '7 days remaining!'));
Condition Types #
// Field comparison (declarative)
.whenEquals('status', 'active')
.whenGreaterThan('score', 100)
.whenLessThan('stock', 10)
.whenField('category', CompareOp.contains, 'premium')
.whenField('role', CompareOp.inList, ['admin', 'moderator'])
// Lambda (flexible)
.when('custom_check', (evt, ctx) {
return evt.data['total'] > 1000 && ctx.get<String>('user_type') == 'vip';
})
MatchMode — Condition Matching Modes #
// All conditions must be true (AND) — default
.withMatchMode(MatchMode.all)
// At least one condition must be true (OR)
.withMatchMode(MatchMode.any)
// No conditions should be true (NOT)
.withMatchMode(MatchMode.none)
// Exactly one condition must be true
.withMatchMode(MatchMode.exactlyOne)
Middleware #
// Logging middleware
engine.use(0, (ctx, next) async {
print('Event started: ${ctx.currentEvent?.eventType}');
final start = DateTime.now();
await next();
final elapsed = DateTime.now().difference(start).inMilliseconds;
print('Event completed: ${elapsed}ms');
});
// Auth middleware
engine.use(-10, (ctx, next) async {
if (ctx.get<bool>('isAuthenticated') != true) {
ctx.stopPipeline = true;
return;
}
await next();
});
Direct Listener (Without Rules) #
engine.on('order.created', (evt, ctx) async {
print('Order received: ${evt.data['order_id']}');
});
Dynamic Rule Management #
// Individual rules
engine.disableRule('seasonal_discount');
engine.enableRule('seasonal_discount');
engine.removeRule('old_rule');
// Entire groups
engine.disableGroup('marketing');
engine.enableGroup('marketing');
// Add a new rule at runtime
engine.addRule(
Rule.create('flash_sale')
.inGroup('marketing')
.on('order.created')
.whenGreaterThan('total', 100.0)
.then('apply_discount', (s) => s.set('percent', 20)),
);
Flow Control #
// Stop the entire pipeline (remaining rules will not execute)
engine.registerInlineAction('validate', (ctx, s) async {
if (ctx.currentEvent!.data['valid'] != true) {
ctx.stopPipeline = true;
}
});
// Skip only the remaining actions of the current rule
engine.registerInlineAction('conditional_skip', (ctx, s) async {
if (someCondition) {
ctx.skipRemainingActions = true;
}
});
Context — Sharing Data Between Actions #
engine.registerInlineAction('calculate', (ctx, s) async {
ctx.set('total', 1500);
});
engine.registerInlineAction('apply_tax', (ctx, s) async {
final total = ctx.get<int>('total')!;
ctx.set('totalWithTax', total * 1.18);
});
// When both run sequentially in the same event, they share data via context
Reading Results #
final result = await engine.fire('order.created', (e) => e.set('total', 7500));
print('Fired: ${result.firedRules.length}');
print('Skipped: ${result.skippedRules.length}');
print('Pipeline stopped: ${result.pipelineStopped}');
print('Duration: ${result.duration.inMilliseconds}ms');
for (final r in result.firedRules) {
print(' ${r.ruleId} → ${r.executedActions.join(', ')}');
}
for (final r in result.skippedRules) {
print(' ${r.ruleId} → failed: ${r.failedConditions.join(', ')}');
}
Export List #
import 'package:are_engine_core/are_engine_core.dart';
// Classes: AreEngine, AreContext, GameEvent, Rule, ActionSettings,
// FieldCondition, DataCondition, ActionBinding
// Results: EngineResult, RuleResult
// Enums: MatchMode, CompareOp
// Interfaces: IEvent, IAction, ICondition, IMiddleware, IRule
Tests #
dart test
17 tests covering: engine, conditions, MatchMode, middleware, pipeline control, group management, context sharing, and result reporting.
License #
MIT