are-engine-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +353 -0
- package/dist/are-core.d.ts +147 -0
- package/dist/are-core.js +360 -0
- package/dist/are-core.mjs +0 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# are-core — Action Rule Event Engine
|
|
2
|
+
|
|
3
|
+
Sıfır bağımlılık, hafif olay-kural-eylem motoru.
|
|
4
|
+
|
|
5
|
+
Browser, Node.js, React, Vue, React Native, Electron — her JS/TS ortamında çalışır.
|
|
6
|
+
|
|
7
|
+
> Bu paket, C# [ARE.Core](../ARE.Core/) motorunun JavaScript/TypeScript portudur.
|
|
8
|
+
> Aynı mimari, aynı API, aynı davranış.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Kurulum
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install are-core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Build adımı yok. TypeScript tip tanımları dahil gelir.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Hızlı Başlangıç
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const { AreEngine, Rule } = require('are-core');
|
|
26
|
+
// veya
|
|
27
|
+
// import { AreEngine, Rule } from 'are-core';
|
|
28
|
+
|
|
29
|
+
// 1) Engine oluştur
|
|
30
|
+
const engine = new AreEngine();
|
|
31
|
+
|
|
32
|
+
// 2) Action kaydet
|
|
33
|
+
engine.registerAction('send_email', async (ctx, s) => {
|
|
34
|
+
console.log('Email gönderildi:', s.get('template'));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// 3) Kural tanımla
|
|
38
|
+
engine.addRule(
|
|
39
|
+
Rule.create('vip_order')
|
|
40
|
+
.on('order.created')
|
|
41
|
+
.whenGreaterThan('total', 5000)
|
|
42
|
+
.then('send_email', s => s.set('template', 'vip_welcome'))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// 4) Event fırlat
|
|
46
|
+
await engine.fire('order.created', e => e.set('total', 7500));
|
|
47
|
+
// Çıktı: Email gönderildi: vip_welcome
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Detaylı Kullanım
|
|
53
|
+
|
|
54
|
+
### Action Tanımlama — Obje ile
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const damageAction = {
|
|
58
|
+
actionType: 'damage',
|
|
59
|
+
execute: async (ctx, settings) => {
|
|
60
|
+
const amount = settings.get('amount');
|
|
61
|
+
console.log(`${amount} hasar verildi!`);
|
|
62
|
+
ctx.set('lastDamage', amount);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
engine.registerAction(damageAction);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Action Tanımlama — Inline
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
engine.registerAction('log', async (ctx, s) => {
|
|
73
|
+
console.log(s.get('message'));
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Kural Tanımlama — Fluent Builder
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
engine.addRule(
|
|
81
|
+
Rule.create('boss_room')
|
|
82
|
+
.inGroup('spawning')
|
|
83
|
+
.withPriority(10)
|
|
84
|
+
.on('player.enter_zone')
|
|
85
|
+
.withMatchMode(MatchMode.All)
|
|
86
|
+
.whenEquals('zone_type', 'boss')
|
|
87
|
+
.when('level_check', (evt) => (evt.data.player_level ?? 0) >= 5)
|
|
88
|
+
.then('spawn_enemy', s => s.set('type', 'dragon').set('count', 1))
|
|
89
|
+
.then('play_sound', s => s.set('clip', 'boss_roar'))
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Birden Fazla Event Dinleme
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
Rule.create('license_warning')
|
|
97
|
+
.on('app.started', 'license.checked')
|
|
98
|
+
.when('expiring', (evt) => (evt.data.days_remaining ?? 999) <= 7)
|
|
99
|
+
.then('show_notification', s => s
|
|
100
|
+
.set('title', 'Lisans Uyarısı')
|
|
101
|
+
.set('message', '7 gün kaldı!'))
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Koşul Tipleri
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
// Alan karşılaştırma (deklaratif)
|
|
108
|
+
.whenEquals('status', 'active')
|
|
109
|
+
.whenGreaterThan('score', 100)
|
|
110
|
+
.whenLessThan('stock', 10)
|
|
111
|
+
.whenField('category', CompareOp.Contains, 'premium')
|
|
112
|
+
.whenField('role', CompareOp.In, ['admin', 'moderator'])
|
|
113
|
+
|
|
114
|
+
// Lambda (esnek)
|
|
115
|
+
.when('custom_check', (evt, ctx) => {
|
|
116
|
+
return evt.data.total > 1000 && ctx.get('user_type') === 'vip';
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### MatchMode — Koşul Eşleşme Modları
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const { MatchMode } = require('are-core');
|
|
124
|
+
|
|
125
|
+
// Tüm koşullar doğru olmalı (AND) — varsayılan
|
|
126
|
+
.withMatchMode(MatchMode.All)
|
|
127
|
+
|
|
128
|
+
// En az bir koşul doğru olmalı (OR)
|
|
129
|
+
.withMatchMode(MatchMode.Any)
|
|
130
|
+
|
|
131
|
+
// Hiçbir koşul doğru olmamalı (NOT)
|
|
132
|
+
.withMatchMode(MatchMode.None)
|
|
133
|
+
|
|
134
|
+
// Tam olarak bir koşul doğru olmalı
|
|
135
|
+
.withMatchMode(MatchMode.ExactlyOne)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Middleware
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Loglama
|
|
142
|
+
engine.use(0, async (ctx, next) => {
|
|
143
|
+
console.log('Event başladı:', ctx.currentEvent.eventType);
|
|
144
|
+
const start = Date.now();
|
|
145
|
+
await next();
|
|
146
|
+
console.log('Event bitti:', (Date.now() - start) + 'ms');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Auth kontrolü
|
|
150
|
+
engine.use(-10, async (ctx, next) => {
|
|
151
|
+
if (!ctx.get('isAuthenticated')) {
|
|
152
|
+
ctx.stopPipeline = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
await next();
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Doğrudan Listener (Kural Olmadan)
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
engine.on('order.created', async (evt, ctx) => {
|
|
163
|
+
console.log('Sipariş:', evt.data.order_id);
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Dinamik Kural Yönetimi
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// Tek kural
|
|
171
|
+
engine.disableRule('seasonal_discount');
|
|
172
|
+
engine.enableRule('seasonal_discount');
|
|
173
|
+
engine.removeRule('old_rule');
|
|
174
|
+
|
|
175
|
+
// Grup toplu
|
|
176
|
+
engine.disableGroup('marketing');
|
|
177
|
+
engine.enableGroup('marketing');
|
|
178
|
+
|
|
179
|
+
// Runtime'da yeni kural ekle
|
|
180
|
+
engine.addRule(
|
|
181
|
+
Rule.create('flash_sale')
|
|
182
|
+
.inGroup('marketing')
|
|
183
|
+
.on('order.created')
|
|
184
|
+
.whenGreaterThan('total', 100)
|
|
185
|
+
.then('apply_discount', s => s.set('percent', 20))
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Akış Kontrolü
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
// Pipeline'ı tamamen durdur (sonraki kurallar çalışmaz)
|
|
193
|
+
engine.registerAction('validate', async (ctx) => {
|
|
194
|
+
if (!ctx.currentEvent.data.valid) {
|
|
195
|
+
ctx.stopPipeline = true;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Sadece mevcut kuralın kalan action'larını atla
|
|
200
|
+
engine.registerAction('conditional_skip', async (ctx) => {
|
|
201
|
+
if (someCondition) {
|
|
202
|
+
ctx.skipRemainingActions = true;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Context — Action'lar Arası Veri Paylaşımı
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
engine.registerAction('calculate', async (ctx) => {
|
|
211
|
+
ctx.set('total', 1500);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
engine.registerAction('apply_tax', async (ctx) => {
|
|
215
|
+
const total = ctx.get('total');
|
|
216
|
+
ctx.set('totalWithTax', total * 1.18);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// İkisi aynı event'te sırayla çalışırsa, context üzerinden veri paylaşır
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Sonuç Okuma
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
const result = await engine.fire('order.created', e => e.set('total', 7500));
|
|
226
|
+
|
|
227
|
+
console.log('Tetiklenen:', result.firedRules.length);
|
|
228
|
+
console.log('Atlanan:', result.skippedRules.length);
|
|
229
|
+
console.log('Pipeline durdu mu:', result.pipelineStopped);
|
|
230
|
+
console.log('Süre:', result.duration + 'ms');
|
|
231
|
+
|
|
232
|
+
result.firedRules.forEach(r => {
|
|
233
|
+
console.log(` ${r.ruleId} → ${r.executedActions.join(', ')}`);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
result.skippedRules.forEach(r => {
|
|
237
|
+
console.log(` ${r.ruleId} → sağlanmayan: ${r.failedConditions.join(', ')}`);
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## React Kullanımı
|
|
244
|
+
|
|
245
|
+
```jsx
|
|
246
|
+
import { AreEngine, Rule, GameEvent, AreContext } from 'are-core';
|
|
247
|
+
import { useRef, useState } from 'react';
|
|
248
|
+
|
|
249
|
+
function useAreEngine(setup) {
|
|
250
|
+
const engineRef = useRef(null);
|
|
251
|
+
if (!engineRef.current) {
|
|
252
|
+
engineRef.current = new AreEngine();
|
|
253
|
+
setup(engineRef.current);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const fire = async (eventType, data) => {
|
|
257
|
+
const ctx = new AreContext();
|
|
258
|
+
const evt = new GameEvent(eventType);
|
|
259
|
+
Object.entries(data).forEach(([k, v]) => evt.set(k, v));
|
|
260
|
+
return await engineRef.current.fire(evt, ctx);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
return { fire, engine: engineRef.current };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Kullanım
|
|
267
|
+
function App() {
|
|
268
|
+
const { fire } = useAreEngine((engine) => {
|
|
269
|
+
engine.registerAction('toast', async (ctx, s) => {
|
|
270
|
+
ctx.set('toast', s.get('message'));
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
engine.addRule(
|
|
274
|
+
Rule.create('big_order')
|
|
275
|
+
.on('cart.checkout')
|
|
276
|
+
.whenGreaterThan('total', 500)
|
|
277
|
+
.then('toast', s => s.set('message', '🎉 Kargo bedava!'))
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return <button onClick={() => fire('cart.checkout', { total: 700 })}>Ödeme;
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Export Listesi
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
const {
|
|
291
|
+
AreEngine, // Çekirdek motor
|
|
292
|
+
AreContext, // Paylaşılan veri çantası
|
|
293
|
+
GameEvent, // Varsayılan event implementasyonu
|
|
294
|
+
Rule, // Fluent kural builder
|
|
295
|
+
ActionSettings, // Action parametreleri
|
|
296
|
+
FieldCondition, // Alan karşılaştırma koşulu
|
|
297
|
+
MatchMode, // All, Any, None, ExactlyOne
|
|
298
|
+
CompareOp, // Equal, GreaterThan, Contains, In vb.
|
|
299
|
+
} = require('are-core');
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## TypeScript Desteği
|
|
305
|
+
|
|
306
|
+
Tip tanımları (`are-core.d.ts`) pakete dahildir. Ek kurulum gerekmez.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { AreEngine, Rule, IAction, IEvent, AreContext, ActionSettings } from 'are-core';
|
|
310
|
+
|
|
311
|
+
// Kendi action tipini tanımla
|
|
312
|
+
const myAction: IAction = {
|
|
313
|
+
actionType: 'my_action',
|
|
314
|
+
execute: async (ctx: AreContext, settings: ActionSettings): Promise => {
|
|
315
|
+
const value = settings.get('key');
|
|
316
|
+
ctx.set('result', value);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Farklı Ortamlarda Import
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
// CommonJS (Node.js, Electron)
|
|
327
|
+
const { AreEngine, Rule } = require('are-core');
|
|
328
|
+
|
|
329
|
+
// ES Module (React, Vue, Angular, Vite, Next.js)
|
|
330
|
+
import { AreEngine, Rule } from 'are-core';
|
|
331
|
+
|
|
332
|
+
// Script tag (browser - global)
|
|
333
|
+
// dist/are-core.js dosyasını doğrudan kullanabilirsin
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Testler
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npm test
|
|
343
|
+
# veya
|
|
344
|
+
node test/test.js
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
17 test: engine, koşullar, MatchMode, middleware, pipeline kontrolü, grup yönetimi, context paylaşımı.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Lisans
|
|
352
|
+
|
|
353
|
+
MIT
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// ARE.Core Type Definitions
|
|
2
|
+
|
|
3
|
+
export declare enum MatchMode {
|
|
4
|
+
All = 'all',
|
|
5
|
+
Any = 'any',
|
|
6
|
+
None = 'none',
|
|
7
|
+
ExactlyOne = 'exactlyOne',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare enum CompareOp {
|
|
11
|
+
Equal = 'eq',
|
|
12
|
+
NotEqual = 'neq',
|
|
13
|
+
GreaterThan = 'gt',
|
|
14
|
+
GreaterOrEqual = 'gte',
|
|
15
|
+
LessThan = 'lt',
|
|
16
|
+
LessOrEqual = 'lte',
|
|
17
|
+
Contains = 'contains',
|
|
18
|
+
StartsWith = 'startsWith',
|
|
19
|
+
In = 'in',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IEvent {
|
|
23
|
+
eventType: string;
|
|
24
|
+
data: Record<string, any>;
|
|
25
|
+
timestamp: Date;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IAction {
|
|
29
|
+
actionType: string;
|
|
30
|
+
execute(context: AreContext, settings: ActionSettings): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ICondition {
|
|
34
|
+
name: string;
|
|
35
|
+
evaluate(evt: IEvent, context: AreContext): boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface IMiddleware {
|
|
39
|
+
order: number;
|
|
40
|
+
process(context: AreContext, next: () => Promise<void>): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ActionBinding {
|
|
44
|
+
actionType: string;
|
|
45
|
+
settings: ActionSettings;
|
|
46
|
+
order: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface EngineResult {
|
|
50
|
+
event: IEvent;
|
|
51
|
+
firedRules: RuleResult[];
|
|
52
|
+
skippedRules: RuleResult[];
|
|
53
|
+
pipelineStopped: boolean;
|
|
54
|
+
duration: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RuleResult {
|
|
58
|
+
ruleId: string;
|
|
59
|
+
conditionsMet: boolean;
|
|
60
|
+
executedActions: string[];
|
|
61
|
+
failedConditions: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Classes ──
|
|
65
|
+
|
|
66
|
+
export declare class AreContext {
|
|
67
|
+
currentEvent: IEvent | null;
|
|
68
|
+
currentRule: Rule | null;
|
|
69
|
+
stopPipeline: boolean;
|
|
70
|
+
skipRemainingActions: boolean;
|
|
71
|
+
set<T>(key: string, value: T): void;
|
|
72
|
+
get<T>(key: string): T | undefined;
|
|
73
|
+
has(key: string): boolean;
|
|
74
|
+
remove(key: string): void;
|
|
75
|
+
clear(): void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export declare class ActionSettings {
|
|
79
|
+
set(key: string, value: any): this;
|
|
80
|
+
get<T>(key: string): T | undefined;
|
|
81
|
+
has(key: string): boolean;
|
|
82
|
+
all(): Record<string, any>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export declare class GameEvent implements IEvent {
|
|
86
|
+
readonly eventType: string;
|
|
87
|
+
readonly data: Record<string, any>;
|
|
88
|
+
readonly timestamp: Date;
|
|
89
|
+
constructor(eventType: string);
|
|
90
|
+
set(key: string, value: any): this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export declare class FieldCondition implements ICondition {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
readonly fieldName: string;
|
|
96
|
+
readonly operator: CompareOp;
|
|
97
|
+
readonly expected: any;
|
|
98
|
+
constructor(fieldName: string, op: CompareOp, expected: any);
|
|
99
|
+
evaluate(evt: IEvent, context: AreContext): boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export declare class Rule {
|
|
103
|
+
ruleId: string;
|
|
104
|
+
group: string | null;
|
|
105
|
+
priority: number;
|
|
106
|
+
isEnabled: boolean;
|
|
107
|
+
eventTypes: string[];
|
|
108
|
+
conditions: ICondition[];
|
|
109
|
+
matchMode: MatchMode;
|
|
110
|
+
actions: ActionBinding[];
|
|
111
|
+
|
|
112
|
+
static create(ruleId: string): Rule;
|
|
113
|
+
inGroup(group: string): this;
|
|
114
|
+
withPriority(priority: number): this;
|
|
115
|
+
enable(): this;
|
|
116
|
+
disable(): this;
|
|
117
|
+
on(...eventTypes: string[]): this;
|
|
118
|
+
withMatchMode(mode: MatchMode): this;
|
|
119
|
+
when(name: string, predicate: (evt: IEvent, ctx: AreContext) => boolean): this;
|
|
120
|
+
when(condition: ICondition): this;
|
|
121
|
+
whenField(field: string, op: CompareOp, expected: any): this;
|
|
122
|
+
whenEquals(field: string, value: any): this;
|
|
123
|
+
whenGreaterThan(field: string, value: any): this;
|
|
124
|
+
whenLessThan(field: string, value: any): this;
|
|
125
|
+
then(actionType: string, order?: number): this;
|
|
126
|
+
then(actionType: string, configure: (s: ActionSettings) => void, order?: number): this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export declare class AreEngine {
|
|
130
|
+
onLog: ((msg: string) => void) | null;
|
|
131
|
+
|
|
132
|
+
registerAction(action: IAction): this;
|
|
133
|
+
registerAction(actionType: string, handler: (ctx: AreContext, s: ActionSettings) => Promise<void>): this;
|
|
134
|
+
addRule(rule: Rule): this;
|
|
135
|
+
addRules(...rules: Rule[]): this;
|
|
136
|
+
use(order: number, handler: (ctx: AreContext, next: () => Promise<void>) => Promise<void>): this;
|
|
137
|
+
on(eventType: string, handler: (evt: IEvent, ctx: AreContext) => Promise<void>): this;
|
|
138
|
+
|
|
139
|
+
enableRule(id: string): this;
|
|
140
|
+
disableRule(id: string): this;
|
|
141
|
+
removeRule(id: string): this;
|
|
142
|
+
enableGroup(group: string): this;
|
|
143
|
+
disableGroup(group: string): this;
|
|
144
|
+
|
|
145
|
+
fire(evt: IEvent, context?: AreContext): Promise<EngineResult>;
|
|
146
|
+
fire(eventType: string, configure?: (e: GameEvent) => void, context?: AreContext): Promise<EngineResult>;
|
|
147
|
+
}
|
package/dist/are-core.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// ARE.Core - Action Rule Event Engine
|
|
5
|
+
// Zero dependency - Browser, Node.js, React Native, Electron
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
// ── Enums ──
|
|
9
|
+
|
|
10
|
+
const MatchMode = Object.freeze({
|
|
11
|
+
All: 'all',
|
|
12
|
+
Any: 'any',
|
|
13
|
+
None: 'none',
|
|
14
|
+
ExactlyOne: 'exactlyOne',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const CompareOp = Object.freeze({
|
|
18
|
+
Equal: 'eq',
|
|
19
|
+
NotEqual: 'neq',
|
|
20
|
+
GreaterThan: 'gt',
|
|
21
|
+
GreaterOrEqual: 'gte',
|
|
22
|
+
LessThan: 'lt',
|
|
23
|
+
LessOrEqual: 'lte',
|
|
24
|
+
Contains: 'contains',
|
|
25
|
+
StartsWith: 'startsWith',
|
|
26
|
+
In: 'in',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ── AreContext ──
|
|
30
|
+
|
|
31
|
+
class AreContext {
|
|
32
|
+
constructor() {
|
|
33
|
+
this._data = new Map();
|
|
34
|
+
this.currentEvent = null;
|
|
35
|
+
this.currentRule = null;
|
|
36
|
+
this.stopPipeline = false;
|
|
37
|
+
this.skipRemainingActions = false;
|
|
38
|
+
}
|
|
39
|
+
set(key, value) { this._data.set(key, value); }
|
|
40
|
+
get(key) { return this._data.get(key); }
|
|
41
|
+
has(key) { return this._data.has(key); }
|
|
42
|
+
remove(key) { this._data.delete(key); }
|
|
43
|
+
clear() { this._data.clear(); }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── ActionSettings ──
|
|
47
|
+
|
|
48
|
+
class ActionSettings {
|
|
49
|
+
constructor() { this._values = {}; }
|
|
50
|
+
set(key, value) { this._values[key] = value; return this; }
|
|
51
|
+
get(key) { return this._values[key]; }
|
|
52
|
+
has(key) { return key in this._values; }
|
|
53
|
+
all() { return Object.assign({}, this._values); }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── GameEvent ──
|
|
57
|
+
|
|
58
|
+
class GameEvent {
|
|
59
|
+
constructor(eventType) {
|
|
60
|
+
this.eventType = eventType;
|
|
61
|
+
this.data = {};
|
|
62
|
+
this.timestamp = new Date();
|
|
63
|
+
}
|
|
64
|
+
set(key, value) { this.data[key] = value; return this; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── FieldCondition ──
|
|
68
|
+
|
|
69
|
+
class FieldCondition {
|
|
70
|
+
constructor(fieldName, operator, expected) {
|
|
71
|
+
this.name = fieldName + ' ' + operator + ' ' + expected;
|
|
72
|
+
this.fieldName = fieldName;
|
|
73
|
+
this.operator = operator;
|
|
74
|
+
this.expected = expected;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
evaluate(evt, _context) {
|
|
78
|
+
var actual = evt.data[this.fieldName];
|
|
79
|
+
if (actual === undefined) return false;
|
|
80
|
+
|
|
81
|
+
switch (this.operator) {
|
|
82
|
+
case CompareOp.Equal: return actual === this.expected;
|
|
83
|
+
case CompareOp.NotEqual: return actual !== this.expected;
|
|
84
|
+
case CompareOp.GreaterThan: return actual > this.expected;
|
|
85
|
+
case CompareOp.GreaterOrEqual: return actual >= this.expected;
|
|
86
|
+
case CompareOp.LessThan: return actual < this.expected;
|
|
87
|
+
case CompareOp.LessOrEqual: return actual <= this.expected;
|
|
88
|
+
case CompareOp.Contains: return String(actual).includes(String(this.expected));
|
|
89
|
+
case CompareOp.StartsWith: return String(actual).startsWith(String(this.expected));
|
|
90
|
+
case CompareOp.In: return Array.isArray(this.expected) && this.expected.includes(actual);
|
|
91
|
+
default: return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Rule (Fluent Builder) ──
|
|
97
|
+
|
|
98
|
+
class Rule {
|
|
99
|
+
constructor(ruleId) {
|
|
100
|
+
this.ruleId = ruleId;
|
|
101
|
+
this.group = null;
|
|
102
|
+
this.priority = 0;
|
|
103
|
+
this.isEnabled = true;
|
|
104
|
+
this.eventTypes = [];
|
|
105
|
+
this.conditions = [];
|
|
106
|
+
this.matchMode = MatchMode.All;
|
|
107
|
+
this.actions = [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static create(ruleId) { return new Rule(ruleId); }
|
|
111
|
+
|
|
112
|
+
inGroup(group) { this.group = group; return this; }
|
|
113
|
+
withPriority(p) { this.priority = p; return this; }
|
|
114
|
+
enable() { this.isEnabled = true; return this; }
|
|
115
|
+
disable() { this.isEnabled = false; return this; }
|
|
116
|
+
|
|
117
|
+
on() {
|
|
118
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
119
|
+
var et = arguments[i];
|
|
120
|
+
if (this.eventTypes.indexOf(et) === -1) this.eventTypes.push(et);
|
|
121
|
+
}
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
withMatchMode(mode) { this.matchMode = mode; return this; }
|
|
126
|
+
|
|
127
|
+
when(nameOrCondition, predicate) {
|
|
128
|
+
if (typeof nameOrCondition === 'object' && nameOrCondition.evaluate) {
|
|
129
|
+
this.conditions.push(nameOrCondition);
|
|
130
|
+
} else {
|
|
131
|
+
this.conditions.push({ name: nameOrCondition, evaluate: predicate });
|
|
132
|
+
}
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
whenField(field, op, expected) {
|
|
137
|
+
this.conditions.push(new FieldCondition(field, op, expected));
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
whenEquals(field, value) { return this.whenField(field, CompareOp.Equal, value); }
|
|
142
|
+
whenGreaterThan(field, value) { return this.whenField(field, CompareOp.GreaterThan, value); }
|
|
143
|
+
whenLessThan(field, value) { return this.whenField(field, CompareOp.LessThan, value); }
|
|
144
|
+
|
|
145
|
+
then(actionType, configure, order) {
|
|
146
|
+
var settings = new ActionSettings();
|
|
147
|
+
if (typeof configure === 'function') {
|
|
148
|
+
configure(settings);
|
|
149
|
+
} else if (typeof configure === 'number') {
|
|
150
|
+
order = configure;
|
|
151
|
+
}
|
|
152
|
+
this.actions.push({ actionType: actionType, settings: settings, order: order || 0 });
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── AreEngine ──
|
|
158
|
+
|
|
159
|
+
class AreEngine {
|
|
160
|
+
constructor() {
|
|
161
|
+
this._actions = new Map();
|
|
162
|
+
this._rules = [];
|
|
163
|
+
this._middlewares = [];
|
|
164
|
+
this._listeners = new Map();
|
|
165
|
+
this.onLog = null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// -- Kayıt --
|
|
169
|
+
|
|
170
|
+
registerAction(actionTypeOrObj, handler) {
|
|
171
|
+
if (typeof actionTypeOrObj === 'string') {
|
|
172
|
+
this._actions.set(actionTypeOrObj, { actionType: actionTypeOrObj, execute: handler });
|
|
173
|
+
} else {
|
|
174
|
+
this._actions.set(actionTypeOrObj.actionType, actionTypeOrObj);
|
|
175
|
+
}
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
addRule(rule) { this._rules.push(rule); return this; }
|
|
180
|
+
|
|
181
|
+
addRules() {
|
|
182
|
+
for (var i = 0; i < arguments.length; i++) this._rules.push(arguments[i]);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
use(order, handler) {
|
|
187
|
+
this._middlewares.push({ order: order, process: handler });
|
|
188
|
+
this._middlewares.sort(function (a, b) { return a.order - b.order; });
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
on(eventType, handler) {
|
|
193
|
+
if (!this._listeners.has(eventType)) this._listeners.set(eventType, []);
|
|
194
|
+
this._listeners.get(eventType).push(handler);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// -- Kural Yönetimi --
|
|
199
|
+
|
|
200
|
+
enableRule(id) { var r = this._rules.find(function (r) { return r.ruleId === id; }); if (r) r.isEnabled = true; return this; }
|
|
201
|
+
disableRule(id) { var r = this._rules.find(function (r) { return r.ruleId === id; }); if (r) r.isEnabled = false; return this; }
|
|
202
|
+
removeRule(id) { this._rules = this._rules.filter(function (r) { return r.ruleId !== id; }); return this; }
|
|
203
|
+
|
|
204
|
+
enableGroup(g) {
|
|
205
|
+
this._rules.forEach(function (r) { if (r.group === g) r.isEnabled = true; });
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
disableGroup(g) {
|
|
210
|
+
this._rules.forEach(function (r) { if (r.group === g) r.isEnabled = false; });
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// -- Event Firing --
|
|
215
|
+
|
|
216
|
+
async fire(eventTypeOrEvent, configure, context) {
|
|
217
|
+
var evt;
|
|
218
|
+
if (typeof eventTypeOrEvent === 'string') {
|
|
219
|
+
evt = new GameEvent(eventTypeOrEvent);
|
|
220
|
+
if (typeof configure === 'function') configure(evt);
|
|
221
|
+
} else {
|
|
222
|
+
evt = eventTypeOrEvent;
|
|
223
|
+
if (configure instanceof AreContext) context = configure;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
var start = Date.now();
|
|
227
|
+
var ctx = context || new AreContext();
|
|
228
|
+
ctx.currentEvent = evt;
|
|
229
|
+
ctx.stopPipeline = false;
|
|
230
|
+
|
|
231
|
+
var result = {
|
|
232
|
+
event: evt,
|
|
233
|
+
firedRules: [],
|
|
234
|
+
skippedRules: [],
|
|
235
|
+
pipelineStopped: false,
|
|
236
|
+
duration: 0
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
var self = this;
|
|
240
|
+
|
|
241
|
+
var coreProcess = async function () {
|
|
242
|
+
// 1) Doğrudan dinleyiciler
|
|
243
|
+
var listeners = self._listeners.get(evt.eventType) || [];
|
|
244
|
+
for (var i = 0; i < listeners.length; i++) {
|
|
245
|
+
if (ctx.stopPipeline) break;
|
|
246
|
+
await listeners[i](evt, ctx);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 2) Eşleşen kurallar (önceliğe göre)
|
|
250
|
+
var matching = self._rules
|
|
251
|
+
.filter(function (r) { return r.isEnabled && r.eventTypes.indexOf(evt.eventType) !== -1; })
|
|
252
|
+
.sort(function (a, b) { return b.priority - a.priority; });
|
|
253
|
+
|
|
254
|
+
self._log('[ARE] Event \'' + evt.eventType + '\' → ' + matching.length + ' aday kural');
|
|
255
|
+
|
|
256
|
+
for (var j = 0; j < matching.length; j++) {
|
|
257
|
+
if (ctx.stopPipeline) {
|
|
258
|
+
self._log('[ARE] Pipeline durduruldu');
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
var rule = matching[j];
|
|
262
|
+
ctx.currentRule = rule;
|
|
263
|
+
ctx.skipRemainingActions = false;
|
|
264
|
+
|
|
265
|
+
var rr = await self._evaluateAndExecute(rule, evt, ctx);
|
|
266
|
+
if (rr.conditionsMet) result.firedRules.push(rr);
|
|
267
|
+
else result.skippedRules.push(rr);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Middleware zinciri
|
|
272
|
+
var pipeline = coreProcess;
|
|
273
|
+
for (var i = this._middlewares.length - 1; i >= 0; i--) {
|
|
274
|
+
(function (mw, next) {
|
|
275
|
+
pipeline = function () { return mw.process(ctx, next); };
|
|
276
|
+
})(this._middlewares[i], pipeline);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await pipeline();
|
|
280
|
+
|
|
281
|
+
result.pipelineStopped = ctx.stopPipeline;
|
|
282
|
+
result.duration = Date.now() - start;
|
|
283
|
+
this._log('[ARE] Event \'' + evt.eventType + '\' tamamlandı: ' +
|
|
284
|
+
result.firedRules.length + ' tetiklendi, ' +
|
|
285
|
+
result.skippedRules.length + ' atlandı, ' +
|
|
286
|
+
result.duration + 'ms');
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// -- İç mekanizma --
|
|
291
|
+
|
|
292
|
+
async _evaluateAndExecute(rule, evt, ctx) {
|
|
293
|
+
var failedConditions = [];
|
|
294
|
+
var conditionsMet = this._evaluateConditions(rule, evt, ctx, failedConditions);
|
|
295
|
+
|
|
296
|
+
if (!conditionsMet) {
|
|
297
|
+
this._log('[ARE] Kural \'' + rule.ruleId + '\' → koşullar sağlanmadı [' + failedConditions.join(', ') + ']');
|
|
298
|
+
return { ruleId: rule.ruleId, conditionsMet: false, executedActions: [], failedConditions: failedConditions };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
var executedActions = [];
|
|
302
|
+
var ordered = rule.actions.slice().sort(function (a, b) { return a.order - b.order; });
|
|
303
|
+
|
|
304
|
+
for (var i = 0; i < ordered.length; i++) {
|
|
305
|
+
if (ctx.skipRemainingActions || ctx.stopPipeline) break;
|
|
306
|
+
var binding = ordered[i];
|
|
307
|
+
var action = this._actions.get(binding.actionType);
|
|
308
|
+
|
|
309
|
+
if (!action) {
|
|
310
|
+
this._log('[ARE] ⚠ Action \'' + binding.actionType + '\' bulunamadı!');
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
this._log('[ARE] → Action \'' + binding.actionType + '\' çalıştırılıyor');
|
|
316
|
+
await action.execute(ctx, binding.settings);
|
|
317
|
+
executedActions.push(binding.actionType);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
this._log('[ARE] ✗ Action \'' + binding.actionType + '\' hata: ' + err.message);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { ruleId: rule.ruleId, conditionsMet: true, executedActions: executedActions, failedConditions: [] };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
_evaluateConditions(rule, evt, ctx, failed) {
|
|
328
|
+
if (rule.conditions.length === 0) return true;
|
|
329
|
+
|
|
330
|
+
var results = [];
|
|
331
|
+
for (var i = 0; i < rule.conditions.length; i++) {
|
|
332
|
+
var c = rule.conditions[i];
|
|
333
|
+
var r = c.evaluate(evt, ctx);
|
|
334
|
+
if (!r) failed.push(c.name);
|
|
335
|
+
results.push(r);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
switch (rule.matchMode) {
|
|
339
|
+
case MatchMode.All: return results.every(function (r) { return r; });
|
|
340
|
+
case MatchMode.Any: return results.some(function (r) { return r; });
|
|
341
|
+
case MatchMode.None: return results.every(function (r) { return !r; });
|
|
342
|
+
case MatchMode.ExactlyOne: return results.filter(function (r) { return r; }).length === 1;
|
|
343
|
+
default: return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
_log(msg) { if (this.onLog) this.onLog(msg); }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ── Export ──
|
|
351
|
+
module.exports = {
|
|
352
|
+
AreEngine: AreEngine,
|
|
353
|
+
AreContext: AreContext,
|
|
354
|
+
GameEvent: GameEvent,
|
|
355
|
+
Rule: Rule,
|
|
356
|
+
ActionSettings: ActionSettings,
|
|
357
|
+
FieldCondition: FieldCondition,
|
|
358
|
+
MatchMode: MatchMode,
|
|
359
|
+
CompareOp: CompareOp
|
|
360
|
+
};
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "are-engine-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Action Rule Event Engine - Zero dependency, cross-platform, lightweight event-rule-action engine",
|
|
5
|
+
"main": "dist/are-core.js",
|
|
6
|
+
"types": "dist/are-core.d.ts",
|
|
7
|
+
"author": "BeratARPA",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/BeratARPA/ARE"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"require": "./dist/are-core.js",
|
|
15
|
+
"import": "./dist/are-core.mjs",
|
|
16
|
+
"types": "./dist/are-core.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node test/test.js"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"rules-engine",
|
|
24
|
+
"event-driven",
|
|
25
|
+
"action",
|
|
26
|
+
"cross-platform",
|
|
27
|
+
"game-engine",
|
|
28
|
+
"lightweight",
|
|
29
|
+
"zero-dependency"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/",
|
|
34
|
+
"README.md"
|
|
35
|
+
]
|
|
36
|
+
}
|