@statedelta-actions/rules 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,5 @@
1
+ 'use strict';var core=require('@statedelta-actions/core');function M(r,t,e,i,o,h,b,s,u){if(s>=u)return o.push({ruleIndex:h,error:`maxSubRuleDepth (${u}) exceeded`}),i.errors++,false;for(let c=0;c<r.length;c++){let a=r[c];if(a.definition.when){let d;try{d=a.definition.when(t);}catch(g){o.push({ruleIndex:h,error:String(g)}),i.errors++;continue}if(!d)continue}if(a.actionId){let d=e.invoke(a.actionId,b);if(i.directivesApplied+=d.appliedCount,i.directivesSkipped+=d.skippedCount,i.errors+=d.errors.length,d.aborted)return true}if(a.subRules&&a.subRules.length>0&&M(a.subRules,t,e,i,o,h,b,s+1,u))return true}return false}async function $(r,t,e,i,o,h,b,s,u){if(s>=u)return o.push({ruleIndex:h,error:`maxSubRuleDepth (${u}) exceeded`}),i.errors++,false;for(let c=0;c<r.length;c++){let a=r[c];if(a.definition.when){let d;try{d=a.definition.when(t);}catch(g){o.push({ruleIndex:h,error:String(g)}),i.errors++;continue}if(!d)continue}if(a.actionId){let d=await e.invokeAsync(a.actionId,b);if(i.directivesApplied+=d.appliedCount,i.directivesSkipped+=d.skippedCount,i.errors+=d.errors.length,d.aborted)return true}if(a.subRules&&a.subRules.length>0&&await $(a.subRules,t,e,i,o,h,b,s+1,u))return true}return false}function V(){return r=>({$invoker:{ruleId:r.id,priority:r.priority,tags:r.tags??[]}})}function D(r,t,e,i){let o=i?Object.assign({},i):{};for(let h of r)Object.assign(o,h(t,e,o));return o}function L(){return {rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0}}function H(r,t,e,i,o,h,b,s,u,c){return {success:r,aborted:t,abortedBy:e,matched:i,skipped:o,notMatched:h,errors:b,processedCount:s,totalCount:u,counters:c}}function W(r,t){let e=r.filledNames.has("beforeRule"),i=r.filledNames.has("afterRule"),o=r.filledNames.has("onRulesComplete"),h=e||i;return function(s,u,c,a,d){let g=s.length,m=[],n=[],A=[],v=[],p=L(),T=a.length>0,C=true,x=false,_,y=g;c.setContext(u);let k=h?{ctx:u,counters:p}:void 0;for(let f=0;f<g;f++){let R=s[f],E=R.definition.id;if(p.rulesEvaluated++,e)try{let l=d.beforeRule(R.definition,k);if(l==="skip"){n.push(E),p.rulesSkipped++;continue}if(l==="abort"){C=!1,x=!0,_="beforeRule",y=f+1;break}}catch(l){v.push({ruleIndex:f,error:`beforeRule: ${l}`}),p.errors++;}let w;try{w=R.definition.when(u);}catch(l){v.push({ruleIndex:f,error:String(l)}),A.push(E),p.errors++;continue}if(!w){A.push(E);continue}m.push(E),p.rulesMatched++;let S;if(T)try{S=D(a,R.definition,u);}catch(l){v.push({ruleIndex:f,error:`Middleware: ${l}`}),p.errors++;continue}if(R.actionId){let l=c.invoke(R.actionId,S);if(p.directivesApplied+=l.appliedCount,p.directivesSkipped+=l.skippedCount,p.errors+=l.errors.length,i)try{if(d.afterRule(R.definition,l,k)==="abort"){C=!1,x=!0,_="afterRule",y=f+1;break}}catch{}if(l.aborted){C=l.abortedBy==="halt",x=true,_=l.abortedBy,y=f+1;break}}if(R.subRules&&R.subRules.length>0&&M(R.subRules,u,c,p,v,f,S,0,t)){C=false,x=true,_="sub-rule",y=f+1;break}}let I=H(C,x,_,m,n,A,v,y,g,p);if(o)try{d.onRulesComplete(I);}catch{}return I}}function J(r,t){let e=r.filledNames.has("beforeRule"),i=r.filledNames.has("afterRule"),o=r.filledNames.has("onRulesComplete"),h=e||i;return async function(s,u,c,a,d){let g=s.length,m=[],n=[],A=[],v=[],p=L(),T=a.length>0,C=true,x=false,_,y=g;c.setContext(u);let k=h?{ctx:u,counters:p}:void 0;for(let f=0;f<g;f++){let R=s[f],E=R.definition.id;if(p.rulesEvaluated++,e)try{let l=await d.beforeRule(R.definition,k);if(l==="skip"){n.push(E),p.rulesSkipped++;continue}if(l==="abort"){C=!1,x=!0,_="beforeRule",y=f+1;break}}catch(l){v.push({ruleIndex:f,error:`beforeRule: ${l}`}),p.errors++;}let w;try{w=R.definition.when(u);}catch(l){v.push({ruleIndex:f,error:String(l)}),A.push(E),p.errors++;continue}if(!w){A.push(E);continue}m.push(E),p.rulesMatched++;let S;if(T)try{S=D(a,R.definition,u);}catch(l){v.push({ruleIndex:f,error:`Middleware: ${l}`}),p.errors++;continue}if(R.actionId){let l=await c.invokeAsync(R.actionId,S);if(p.directivesApplied+=l.appliedCount,p.directivesSkipped+=l.skippedCount,p.errors+=l.errors.length,i)try{if(await d.afterRule(R.definition,l,k)==="abort"){C=!1,x=!0,_="afterRule",y=f+1;break}}catch{}if(l.aborted){C=l.abortedBy==="halt",x=true,_=l.abortedBy,y=f+1;break}}if(R.subRules&&R.subRules.length>0&&await $(R.subRules,u,c,p,v,f,S,0,t)){C=false,x=true,_="sub-rule",y=f+1;break}}let I=H(C,x,_,m,n,A,v,y,g,p);if(o)try{await d.onRulesComplete(I);}catch{}return I}}function O(r,t,e){return t?J(r,e):W(r,e)}function B(r,t,e,i,o,h,b){r.push(`{const _er={success:${t},aborted:true,abortedBy:${e},matched,skipped,notMatched,errors,processedCount:${i},totalCount:${o},counters};`),h&&r.push(`try{${b}_hookOnComplete(_er);}catch(_e){}`),r.push("return _er;}");}function j(r,t,e,i){let{filledNames:o,asyncNames:h}=r,b=y=>o.has(y),s=y=>h.has(y)?"await ":"",u=b("beforeRule"),c=b("afterRule"),a=b("onRulesComplete"),d=s("beforeRule"),g=s("afterRule"),m=s("onRulesComplete"),n=[];n.push("const len=rules.length;"),n.push("const matched=[];const skipped=[];const notMatched=[];"),n.push("const errors=[];"),n.push("const counters={rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0};"),n.push("ae.setContext(ctx);"),(u||c)&&n.push("const evalCtx={ctx,counters};"),u&&n.push("const _hookBefore=hooks.beforeRule;"),c&&n.push("const _hookAfter=hooks.afterRule;"),a&&n.push("const _hookOnComplete=hooks.onRulesComplete;"),n.push("for(let i=0;i<len;i++){"),n.push("const stored=rules[i];"),n.push("const _rid=stored.definition.id;"),n.push("counters.rulesEvaluated++;"),u&&(n.push("try{"),n.push(`const _bd=${d}_hookBefore(stored.definition,evalCtx);`),n.push('if(_bd==="skip"){skipped.push(_rid);counters.rulesSkipped++;continue;}'),n.push('if(_bd==="abort")'),B(n,"false",'"beforeRule"',"i+1","len",a,m),n.push('}catch(_e){errors.push({ruleIndex:i,error:"beforeRule: "+_e});counters.errors++;}')),n.push("let ev;"),n.push("try{ev=stored.definition.when(ctx);}catch(_e){errors.push({ruleIndex:i,error:String(_e)});notMatched.push(_rid);counters.errors++;continue;}"),n.push("if(!ev){notMatched.push(_rid);continue;}"),n.push("matched.push(_rid);counters.rulesMatched++;"),t&&(n.push("let params;"),n.push('try{params=$.runMw(mw,stored.definition,ctx);}catch(_e){errors.push({ruleIndex:i,error:"Middleware: "+_e});counters.errors++;continue;}'));let A=t?",params":"",v=e?`await ae.invokeAsync(stored.actionId${A})`:`ae.invoke(stored.actionId${A})`;n.push("if(stored.actionId){"),n.push(`const ir=${v};`),n.push("counters.directivesApplied+=ir.appliedCount;counters.directivesSkipped+=ir.skippedCount;counters.errors+=ir.errors.length;"),c&&(n.push("try{"),n.push(`const _ad=${g}_hookAfter(stored.definition,ir,evalCtx);`),n.push('if(_ad==="abort")'),B(n,"false",'"afterRule"',"i+1","len",a,m),n.push("}catch(_e){}")),n.push("if(ir.aborted)"),B(n,'ir.abortedBy==="halt"',"ir.abortedBy","i+1","len",a,m),n.push("}");let p=t?"params":"undefined";n.push("if(stored.subRules&&stored.subRules.length>0){"),n.push(`if(${e?"await ":""}$.evalSub(stored.subRules,ctx,ae,counters,errors,i,${p},0,$.maxDepth))`),B(n,"false",'"sub-rule"',"i+1","len",a,m),n.push("}"),n.push("}"),n.push("const _r={success:true,aborted:false,abortedBy:undefined,matched,skipped,notMatched,errors,processedCount:len,totalCount:len,counters};"),a&&n.push(`try{${m}_hookOnComplete(_r);}catch(_e){}`),n.push("return _r;");let T=n.join(`
2
+ `),C=e?"async ":"",x=new Function("rules","ctx","ae","mw","hooks","$",`"use strict";
3
+ return(${C}()=>{
4
+ ${T}
5
+ })();`),_={maxDepth:i};return _.evalSub=e?$:M,t&&(_.runMw=D),function(k,I,f,R,E){return x(k,I,f,R,E,_)}}function P(r){if(!r.id||typeof r.id!="string")return {ruleId:r.id??"(missing)",code:"INVALID_RULE",message:"rule must have a string id"};if(typeof r.priority!="number")return {ruleId:r.id,code:"INVALID_RULE",message:"rule must have a numeric priority"};if(typeof r.when!="function")return {ruleId:r.id,code:"INVALID_RULE",message:"rule must have a when function"};let t=Array.isArray(r.then)&&r.then.length>0,e=Array.isArray(r.rules)&&r.rules.length>0;return !t&&!e?{ruleId:r.id,code:"INVALID_RULE",message:"rule must have then or sub-rules (or both)"}:null}function U(r,t){let e=`${t}.${r.id??"(missing)"}`;if(!r.id||typeof r.id!="string")return {ruleId:e,code:"INVALID_RULE",message:"sub-rule must have a string id"};let i=Array.isArray(r.then)&&r.then.length>0,o=Array.isArray(r.rules)&&r.rules.length>0;return !i&&!o?{ruleId:e,code:"INVALID_RULE",message:"sub-rule must have then or sub-rules (or both)"}:null}var N=()=>{},F=class{_actionEngine;_middleware;_ruleHooks;_isAsync;_maxSubRuleDepth;_requestedMode;_ruleHookAnalysis;_ruleEvaluator;_mode;_maybeAutoPromote;_ruleRegistry=new Map;_rules=[];_batch=null;constructor(t){if(this._actionEngine=t.actionEngine,this._middleware=t.middleware??[],this._ruleHooks=t.ruleHooks??{},this._maxSubRuleDepth=t.maxSubRuleDepth??10,this._requestedMode=t.mode??"auto",this._ruleHookAnalysis=core.analyzeSlots(this._ruleHooks,core.RULE_SLOT_NAMES),this._isAsync=this._ruleHookAnalysis.hasAnyAsync||this._actionEngine.isAsync,this._requestedMode==="jit")this._ruleEvaluator=this._buildJitRule(),this._mode="jit",this._maybeAutoPromote=N;else if(this._ruleEvaluator=O(this._ruleHookAnalysis,this._isAsync,this._maxSubRuleDepth),this._mode="interpret",this._requestedMode==="auto"){let e=0,i=t.autoJitThreshold??8;this._maybeAutoPromote=()=>{++e>=i&&this._promote();};}else this._maybeAutoPromote=N;}get actionEngine(){return this._actionEngine}get isAsync(){return this._isAsync}get compilationMode(){return this._mode}get size(){return this._rules.length}has(t){return this._ruleRegistry.has(t)}register(t){let e=[],i=[],o=[];for(let s of t){let u=P(s);if(u){i.push(u);continue}if(this._ruleRegistry.has(s.id)){i.push({ruleId:s.id,code:"DUPLICATE_ID",message:`rule "${s.id}" is already registered`});continue}let c=`rule:${s.id}`,a=Array.isArray(s.then)&&s.then.length>0,d=a?c:"",g=Array.isArray(s.rules)&&s.rules.length>0?this._collectSubRules(c,s.rules,i,o):void 0,m={definition:s,actionId:d,priority:s.priority,subRules:g?.length?g:void 0};if(this._ruleRegistry.set(s.id,m),Q(this._rules,m),a){let n=["rule"];s.tags&&n.push(...s.tags),o.push({id:d,tags:n,directives:s.then,declarations:s.declarations});}e.push(s.id);}let h=[];if(o.length>0){let s=this._actionEngine.register(o);for(let u of s.errors){let c=this._actionIdToRuleId(u.actionId);i.push({ruleId:c??u.actionId,code:u.code??"ACTION_ERROR",message:u.error});}h=s.warnings;}let b={registered:e,errors:i,warnings:h};return this._batch&&(this._batch.registered.push(...e),this._batch.errors.push(...i),this._batch.warnings.push(...h)),b}unregister(t){let e=this._ruleRegistry.get(t);return e?(this._ruleRegistry.delete(t),X(this._rules,e),e.actionId&&this._actionEngine.unregister(e.actionId),e.subRules&&this._unregisterSubRules(e.subRules),true):false}beginBatch(){this._batch||(this._batch={depth:0,registered:[],errors:[],warnings:[]}),this._batch.depth++,this._actionEngine.beginBatch();}endBatch(){if(!this._batch||this._batch.depth<=0)throw new Error("endBatch() called without matching beginBatch()");this._batch.depth--;let t=this._actionEngine.endBatch();if(this._batch.depth>0)return {registered:[],errors:[],warnings:[]};let e=this._batch;for(let o of t.errors){let h=this._actionIdToRuleId(o.actionId);e.errors.push({ruleId:h??o.actionId,code:o.code??"ACTION_ERROR",message:o.error});}e.warnings.push(...t.warnings);let i={registered:e.registered,errors:e.errors,warnings:e.warnings};return this._batch=null,i}batch(t){this.beginBatch();try{return t(this),this.endBatch()}catch(e){try{this.endBatch();}catch{}throw e}}evaluate(t){if(this._isAsync)throw new Error("Cannot call evaluate() with async hooks. Use evaluateAsync() instead.");let e=this._ruleEvaluator(this._rules,t,this._actionEngine,this._middleware,this._ruleHooks);return this._maybeAutoPromote(),e}async evaluateAsync(t){let e=await this._ruleEvaluator(this._rules,t,this._actionEngine,this._middleware,this._ruleHooks);return this._maybeAutoPromote(),e}compile(){this._requestedMode!=="interpret"&&this._mode!=="jit"&&this._promote();}_collectSubRules(t,e,i,o){let h=[],b=this._actionIdToRuleId(t);for(let s of e){let u=U(s,b);if(u){i.push(u);continue}let c=`${t}.${s.id}`,a=`${b}.${s.id}`,d=Array.isArray(s.then)&&s.then.length>0,g=Array.isArray(s.rules)&&s.rules.length>0?this._collectSubRules(c,s.rules,i,o):void 0,m={definition:s,actionId:d?c:"",subRules:g?.length?g:void 0};if(d){let n=["rule"];s.tags&&n.push(...s.tags),o.push({id:c,tags:n,directives:s.then,declarations:s.declarations});}this._ruleRegistry.set(a,m),h.push(m);}return h}_unregisterSubRules(t){for(let e of t){if(e.actionId){let i=this._actionIdToRuleId(e.actionId);i&&this._ruleRegistry.delete(i),this._actionEngine.unregister(e.actionId);}e.subRules&&this._unregisterSubRules(e.subRules);}}_actionIdToRuleId(t){return t.startsWith("rule:")?t.slice(5):null}_promote(){this._ruleEvaluator=this._buildJitRule(),this._mode="jit",this._maybeAutoPromote=N;}_buildJitRule(){return j(this._ruleHookAnalysis,this._middleware.length>0,this._isAsync,this._maxSubRuleDepth)}};function K(r){return new F(r)}function Q(r,t){let e=0,i=r.length;for(;e<i;){let o=e+i>>>1;r[o].priority>=t.priority?e=o+1:i=o;}r.splice(e,0,t);}function X(r,t){let e=r.indexOf(t);return e===-1?false:(r.splice(e,1),true)}var Y={analyze:()=>({capabilities:["halt"],dependencies:[]}),execute:()=>({ok:true,halt:true})};exports.HALT_HANDLER=Y;exports.createInvokerMiddleware=V;exports.createRuleEngine=K;
@@ -0,0 +1,117 @@
1
+ import { Directive, RuleError, FrameCounters, HookFn, DirectiveResult } from '@statedelta-actions/core';
2
+ import { RegisterWarning, IActionEngine } from '@statedelta-actions/actions';
3
+
4
+ interface SubRuleDefinition<TCtx = unknown> {
5
+ readonly id: string;
6
+ readonly when?: (ctx: TCtx) => boolean;
7
+ readonly then?: readonly Directive<TCtx>[];
8
+ readonly rules?: readonly SubRuleDefinition<TCtx>[];
9
+ readonly tags?: readonly string[];
10
+ readonly effects?: readonly string[];
11
+ readonly declarations?: Record<string, unknown>;
12
+ readonly metadata?: Record<string, unknown>;
13
+ }
14
+ interface RuleDefinition<TCtx = unknown> extends SubRuleDefinition<TCtx> {
15
+ readonly priority: number;
16
+ readonly rules?: readonly SubRuleDefinition<TCtx>[];
17
+ }
18
+ interface RuleRegisterError {
19
+ readonly ruleId: string;
20
+ readonly code: string;
21
+ readonly message: string;
22
+ }
23
+ interface RuleRegisterResult {
24
+ readonly registered: readonly string[];
25
+ readonly errors: readonly RuleRegisterError[];
26
+ readonly warnings: readonly RegisterWarning[];
27
+ }
28
+ interface RuleEvaluationResult {
29
+ readonly success: boolean;
30
+ readonly aborted: boolean;
31
+ readonly abortedBy?: string;
32
+ readonly matched: readonly string[];
33
+ readonly skipped: readonly string[];
34
+ readonly notMatched: readonly string[];
35
+ readonly errors: readonly RuleError[];
36
+ readonly processedCount: number;
37
+ readonly totalCount: number;
38
+ readonly counters: FrameCounters;
39
+ }
40
+ interface RuleEvaluationContext<TCtx = unknown> {
41
+ readonly ctx: TCtx;
42
+ readonly counters: FrameCounters;
43
+ }
44
+ interface RuleHooks<TCtx = unknown> {
45
+ beforeRule?: HookFn<[
46
+ rule: RuleDefinition<TCtx>,
47
+ evalCtx: RuleEvaluationContext<TCtx>
48
+ ], "skip" | "abort" | void>;
49
+ afterRule?: HookFn<[
50
+ rule: RuleDefinition<TCtx>,
51
+ result: DirectiveResult,
52
+ evalCtx: RuleEvaluationContext<TCtx>
53
+ ], "abort" | void>;
54
+ onRulesComplete?: HookFn<[result: RuleEvaluationResult], void>;
55
+ }
56
+ type RuleMiddleware<TCtx = unknown> = (rule: RuleDefinition<TCtx>, ctx: TCtx, params: Record<string, unknown>) => Record<string, unknown>;
57
+ interface RuleEngineConfig<TCtx = unknown> {
58
+ /** ActionEngine já configurado pelo consumer (handlers, manifest, limits, etc.). */
59
+ actionEngine: IActionEngine<TCtx>;
60
+ /** Middleware global — pipeline entre match e invoke. */
61
+ middleware?: readonly RuleMiddleware<TCtx>[];
62
+ /** Hooks de rule evaluation (beforeRule, afterRule, onRulesComplete). */
63
+ ruleHooks?: RuleHooks<TCtx>;
64
+ /** Profundidade máxima de sub-rules (default: 10). */
65
+ maxSubRuleDepth?: number;
66
+ /** Modo de execução: interpret (sempre), jit (compila no constructor), auto (promove após threshold). Default: "auto". */
67
+ mode?: "interpret" | "jit" | "auto";
68
+ /** Threshold de evaluate() calls para auto-promote em modo "auto". Default: 8. */
69
+ autoJitThreshold?: number;
70
+ }
71
+ interface IRuleEngine<TCtx = unknown> {
72
+ register(rules: readonly RuleDefinition<TCtx>[]): RuleRegisterResult;
73
+ unregister(id: string): boolean;
74
+ evaluate(ctx: TCtx): RuleEvaluationResult;
75
+ evaluateAsync(ctx: TCtx): Promise<RuleEvaluationResult>;
76
+ beginBatch(): void;
77
+ endBatch(): RuleRegisterResult;
78
+ /** Executa fn dentro de um batch, com endBatch() garantido (inclusive em throw). */
79
+ batch(fn: (engine: IRuleEngine<TCtx>) => void): RuleRegisterResult;
80
+ /** Força promoção para JIT (no-op se mode === "interpret"). */
81
+ compile(): void;
82
+ /** Verifica se uma rule está registrada (aceita qualified IDs para sub-rules). */
83
+ has(id: string): boolean;
84
+ readonly actionEngine: IActionEngine<TCtx>;
85
+ readonly isAsync: boolean;
86
+ readonly compilationMode: "interpret" | "jit";
87
+ /** Número de rules top-level registradas. */
88
+ readonly size: number;
89
+ }
90
+
91
+ declare function createRuleEngine<TCtx>(config: RuleEngineConfig<TCtx>): IRuleEngine<TCtx>;
92
+
93
+ /**
94
+ * Halt handler — sinaliza ao ActionEngine para abortar execução de diretivas.
95
+ * O evaluator do RuleEngine interpreta `abortedBy === "halt"` como parada controlada.
96
+ *
97
+ * Uso:
98
+ * ```ts
99
+ * const actionEngine = createActionEngine({
100
+ * handlers: { ...gameHandlers, halt: HALT_HANDLER },
101
+ * });
102
+ * ```
103
+ */
104
+ declare const HALT_HANDLER: {
105
+ analyze: () => {
106
+ capabilities: string[];
107
+ dependencies: string[];
108
+ };
109
+ execute: () => {
110
+ ok: boolean;
111
+ halt: boolean;
112
+ };
113
+ };
114
+
115
+ declare function createInvokerMiddleware<TCtx>(): RuleMiddleware<TCtx>;
116
+
117
+ export { HALT_HANDLER, type IRuleEngine, type RuleDefinition, type RuleEngineConfig, type RuleEvaluationContext, type RuleEvaluationResult, type RuleHooks, type RuleMiddleware, type RuleRegisterError, type RuleRegisterResult, type SubRuleDefinition, createInvokerMiddleware, createRuleEngine };
@@ -0,0 +1,117 @@
1
+ import { Directive, RuleError, FrameCounters, HookFn, DirectiveResult } from '@statedelta-actions/core';
2
+ import { RegisterWarning, IActionEngine } from '@statedelta-actions/actions';
3
+
4
+ interface SubRuleDefinition<TCtx = unknown> {
5
+ readonly id: string;
6
+ readonly when?: (ctx: TCtx) => boolean;
7
+ readonly then?: readonly Directive<TCtx>[];
8
+ readonly rules?: readonly SubRuleDefinition<TCtx>[];
9
+ readonly tags?: readonly string[];
10
+ readonly effects?: readonly string[];
11
+ readonly declarations?: Record<string, unknown>;
12
+ readonly metadata?: Record<string, unknown>;
13
+ }
14
+ interface RuleDefinition<TCtx = unknown> extends SubRuleDefinition<TCtx> {
15
+ readonly priority: number;
16
+ readonly rules?: readonly SubRuleDefinition<TCtx>[];
17
+ }
18
+ interface RuleRegisterError {
19
+ readonly ruleId: string;
20
+ readonly code: string;
21
+ readonly message: string;
22
+ }
23
+ interface RuleRegisterResult {
24
+ readonly registered: readonly string[];
25
+ readonly errors: readonly RuleRegisterError[];
26
+ readonly warnings: readonly RegisterWarning[];
27
+ }
28
+ interface RuleEvaluationResult {
29
+ readonly success: boolean;
30
+ readonly aborted: boolean;
31
+ readonly abortedBy?: string;
32
+ readonly matched: readonly string[];
33
+ readonly skipped: readonly string[];
34
+ readonly notMatched: readonly string[];
35
+ readonly errors: readonly RuleError[];
36
+ readonly processedCount: number;
37
+ readonly totalCount: number;
38
+ readonly counters: FrameCounters;
39
+ }
40
+ interface RuleEvaluationContext<TCtx = unknown> {
41
+ readonly ctx: TCtx;
42
+ readonly counters: FrameCounters;
43
+ }
44
+ interface RuleHooks<TCtx = unknown> {
45
+ beforeRule?: HookFn<[
46
+ rule: RuleDefinition<TCtx>,
47
+ evalCtx: RuleEvaluationContext<TCtx>
48
+ ], "skip" | "abort" | void>;
49
+ afterRule?: HookFn<[
50
+ rule: RuleDefinition<TCtx>,
51
+ result: DirectiveResult,
52
+ evalCtx: RuleEvaluationContext<TCtx>
53
+ ], "abort" | void>;
54
+ onRulesComplete?: HookFn<[result: RuleEvaluationResult], void>;
55
+ }
56
+ type RuleMiddleware<TCtx = unknown> = (rule: RuleDefinition<TCtx>, ctx: TCtx, params: Record<string, unknown>) => Record<string, unknown>;
57
+ interface RuleEngineConfig<TCtx = unknown> {
58
+ /** ActionEngine já configurado pelo consumer (handlers, manifest, limits, etc.). */
59
+ actionEngine: IActionEngine<TCtx>;
60
+ /** Middleware global — pipeline entre match e invoke. */
61
+ middleware?: readonly RuleMiddleware<TCtx>[];
62
+ /** Hooks de rule evaluation (beforeRule, afterRule, onRulesComplete). */
63
+ ruleHooks?: RuleHooks<TCtx>;
64
+ /** Profundidade máxima de sub-rules (default: 10). */
65
+ maxSubRuleDepth?: number;
66
+ /** Modo de execução: interpret (sempre), jit (compila no constructor), auto (promove após threshold). Default: "auto". */
67
+ mode?: "interpret" | "jit" | "auto";
68
+ /** Threshold de evaluate() calls para auto-promote em modo "auto". Default: 8. */
69
+ autoJitThreshold?: number;
70
+ }
71
+ interface IRuleEngine<TCtx = unknown> {
72
+ register(rules: readonly RuleDefinition<TCtx>[]): RuleRegisterResult;
73
+ unregister(id: string): boolean;
74
+ evaluate(ctx: TCtx): RuleEvaluationResult;
75
+ evaluateAsync(ctx: TCtx): Promise<RuleEvaluationResult>;
76
+ beginBatch(): void;
77
+ endBatch(): RuleRegisterResult;
78
+ /** Executa fn dentro de um batch, com endBatch() garantido (inclusive em throw). */
79
+ batch(fn: (engine: IRuleEngine<TCtx>) => void): RuleRegisterResult;
80
+ /** Força promoção para JIT (no-op se mode === "interpret"). */
81
+ compile(): void;
82
+ /** Verifica se uma rule está registrada (aceita qualified IDs para sub-rules). */
83
+ has(id: string): boolean;
84
+ readonly actionEngine: IActionEngine<TCtx>;
85
+ readonly isAsync: boolean;
86
+ readonly compilationMode: "interpret" | "jit";
87
+ /** Número de rules top-level registradas. */
88
+ readonly size: number;
89
+ }
90
+
91
+ declare function createRuleEngine<TCtx>(config: RuleEngineConfig<TCtx>): IRuleEngine<TCtx>;
92
+
93
+ /**
94
+ * Halt handler — sinaliza ao ActionEngine para abortar execução de diretivas.
95
+ * O evaluator do RuleEngine interpreta `abortedBy === "halt"` como parada controlada.
96
+ *
97
+ * Uso:
98
+ * ```ts
99
+ * const actionEngine = createActionEngine({
100
+ * handlers: { ...gameHandlers, halt: HALT_HANDLER },
101
+ * });
102
+ * ```
103
+ */
104
+ declare const HALT_HANDLER: {
105
+ analyze: () => {
106
+ capabilities: string[];
107
+ dependencies: string[];
108
+ };
109
+ execute: () => {
110
+ ok: boolean;
111
+ halt: boolean;
112
+ };
113
+ };
114
+
115
+ declare function createInvokerMiddleware<TCtx>(): RuleMiddleware<TCtx>;
116
+
117
+ export { HALT_HANDLER, type IRuleEngine, type RuleDefinition, type RuleEngineConfig, type RuleEvaluationContext, type RuleEvaluationResult, type RuleHooks, type RuleMiddleware, type RuleRegisterError, type RuleRegisterResult, type SubRuleDefinition, createInvokerMiddleware, createRuleEngine };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import {analyzeSlots,RULE_SLOT_NAMES}from'@statedelta-actions/core';function M(r,t,e,i,o,h,b,s,u){if(s>=u)return o.push({ruleIndex:h,error:`maxSubRuleDepth (${u}) exceeded`}),i.errors++,false;for(let c=0;c<r.length;c++){let a=r[c];if(a.definition.when){let d;try{d=a.definition.when(t);}catch(g){o.push({ruleIndex:h,error:String(g)}),i.errors++;continue}if(!d)continue}if(a.actionId){let d=e.invoke(a.actionId,b);if(i.directivesApplied+=d.appliedCount,i.directivesSkipped+=d.skippedCount,i.errors+=d.errors.length,d.aborted)return true}if(a.subRules&&a.subRules.length>0&&M(a.subRules,t,e,i,o,h,b,s+1,u))return true}return false}async function $(r,t,e,i,o,h,b,s,u){if(s>=u)return o.push({ruleIndex:h,error:`maxSubRuleDepth (${u}) exceeded`}),i.errors++,false;for(let c=0;c<r.length;c++){let a=r[c];if(a.definition.when){let d;try{d=a.definition.when(t);}catch(g){o.push({ruleIndex:h,error:String(g)}),i.errors++;continue}if(!d)continue}if(a.actionId){let d=await e.invokeAsync(a.actionId,b);if(i.directivesApplied+=d.appliedCount,i.directivesSkipped+=d.skippedCount,i.errors+=d.errors.length,d.aborted)return true}if(a.subRules&&a.subRules.length>0&&await $(a.subRules,t,e,i,o,h,b,s+1,u))return true}return false}function V(){return r=>({$invoker:{ruleId:r.id,priority:r.priority,tags:r.tags??[]}})}function D(r,t,e,i){let o=i?Object.assign({},i):{};for(let h of r)Object.assign(o,h(t,e,o));return o}function L(){return {rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0}}function H(r,t,e,i,o,h,b,s,u,c){return {success:r,aborted:t,abortedBy:e,matched:i,skipped:o,notMatched:h,errors:b,processedCount:s,totalCount:u,counters:c}}function W(r,t){let e=r.filledNames.has("beforeRule"),i=r.filledNames.has("afterRule"),o=r.filledNames.has("onRulesComplete"),h=e||i;return function(s,u,c,a,d){let g=s.length,m=[],n=[],A=[],v=[],p=L(),T=a.length>0,C=true,x=false,_,y=g;c.setContext(u);let k=h?{ctx:u,counters:p}:void 0;for(let f=0;f<g;f++){let R=s[f],E=R.definition.id;if(p.rulesEvaluated++,e)try{let l=d.beforeRule(R.definition,k);if(l==="skip"){n.push(E),p.rulesSkipped++;continue}if(l==="abort"){C=!1,x=!0,_="beforeRule",y=f+1;break}}catch(l){v.push({ruleIndex:f,error:`beforeRule: ${l}`}),p.errors++;}let w;try{w=R.definition.when(u);}catch(l){v.push({ruleIndex:f,error:String(l)}),A.push(E),p.errors++;continue}if(!w){A.push(E);continue}m.push(E),p.rulesMatched++;let S;if(T)try{S=D(a,R.definition,u);}catch(l){v.push({ruleIndex:f,error:`Middleware: ${l}`}),p.errors++;continue}if(R.actionId){let l=c.invoke(R.actionId,S);if(p.directivesApplied+=l.appliedCount,p.directivesSkipped+=l.skippedCount,p.errors+=l.errors.length,i)try{if(d.afterRule(R.definition,l,k)==="abort"){C=!1,x=!0,_="afterRule",y=f+1;break}}catch{}if(l.aborted){C=l.abortedBy==="halt",x=true,_=l.abortedBy,y=f+1;break}}if(R.subRules&&R.subRules.length>0&&M(R.subRules,u,c,p,v,f,S,0,t)){C=false,x=true,_="sub-rule",y=f+1;break}}let I=H(C,x,_,m,n,A,v,y,g,p);if(o)try{d.onRulesComplete(I);}catch{}return I}}function J(r,t){let e=r.filledNames.has("beforeRule"),i=r.filledNames.has("afterRule"),o=r.filledNames.has("onRulesComplete"),h=e||i;return async function(s,u,c,a,d){let g=s.length,m=[],n=[],A=[],v=[],p=L(),T=a.length>0,C=true,x=false,_,y=g;c.setContext(u);let k=h?{ctx:u,counters:p}:void 0;for(let f=0;f<g;f++){let R=s[f],E=R.definition.id;if(p.rulesEvaluated++,e)try{let l=await d.beforeRule(R.definition,k);if(l==="skip"){n.push(E),p.rulesSkipped++;continue}if(l==="abort"){C=!1,x=!0,_="beforeRule",y=f+1;break}}catch(l){v.push({ruleIndex:f,error:`beforeRule: ${l}`}),p.errors++;}let w;try{w=R.definition.when(u);}catch(l){v.push({ruleIndex:f,error:String(l)}),A.push(E),p.errors++;continue}if(!w){A.push(E);continue}m.push(E),p.rulesMatched++;let S;if(T)try{S=D(a,R.definition,u);}catch(l){v.push({ruleIndex:f,error:`Middleware: ${l}`}),p.errors++;continue}if(R.actionId){let l=await c.invokeAsync(R.actionId,S);if(p.directivesApplied+=l.appliedCount,p.directivesSkipped+=l.skippedCount,p.errors+=l.errors.length,i)try{if(await d.afterRule(R.definition,l,k)==="abort"){C=!1,x=!0,_="afterRule",y=f+1;break}}catch{}if(l.aborted){C=l.abortedBy==="halt",x=true,_=l.abortedBy,y=f+1;break}}if(R.subRules&&R.subRules.length>0&&await $(R.subRules,u,c,p,v,f,S,0,t)){C=false,x=true,_="sub-rule",y=f+1;break}}let I=H(C,x,_,m,n,A,v,y,g,p);if(o)try{await d.onRulesComplete(I);}catch{}return I}}function O(r,t,e){return t?J(r,e):W(r,e)}function B(r,t,e,i,o,h,b){r.push(`{const _er={success:${t},aborted:true,abortedBy:${e},matched,skipped,notMatched,errors,processedCount:${i},totalCount:${o},counters};`),h&&r.push(`try{${b}_hookOnComplete(_er);}catch(_e){}`),r.push("return _er;}");}function j(r,t,e,i){let{filledNames:o,asyncNames:h}=r,b=y=>o.has(y),s=y=>h.has(y)?"await ":"",u=b("beforeRule"),c=b("afterRule"),a=b("onRulesComplete"),d=s("beforeRule"),g=s("afterRule"),m=s("onRulesComplete"),n=[];n.push("const len=rules.length;"),n.push("const matched=[];const skipped=[];const notMatched=[];"),n.push("const errors=[];"),n.push("const counters={rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0};"),n.push("ae.setContext(ctx);"),(u||c)&&n.push("const evalCtx={ctx,counters};"),u&&n.push("const _hookBefore=hooks.beforeRule;"),c&&n.push("const _hookAfter=hooks.afterRule;"),a&&n.push("const _hookOnComplete=hooks.onRulesComplete;"),n.push("for(let i=0;i<len;i++){"),n.push("const stored=rules[i];"),n.push("const _rid=stored.definition.id;"),n.push("counters.rulesEvaluated++;"),u&&(n.push("try{"),n.push(`const _bd=${d}_hookBefore(stored.definition,evalCtx);`),n.push('if(_bd==="skip"){skipped.push(_rid);counters.rulesSkipped++;continue;}'),n.push('if(_bd==="abort")'),B(n,"false",'"beforeRule"',"i+1","len",a,m),n.push('}catch(_e){errors.push({ruleIndex:i,error:"beforeRule: "+_e});counters.errors++;}')),n.push("let ev;"),n.push("try{ev=stored.definition.when(ctx);}catch(_e){errors.push({ruleIndex:i,error:String(_e)});notMatched.push(_rid);counters.errors++;continue;}"),n.push("if(!ev){notMatched.push(_rid);continue;}"),n.push("matched.push(_rid);counters.rulesMatched++;"),t&&(n.push("let params;"),n.push('try{params=$.runMw(mw,stored.definition,ctx);}catch(_e){errors.push({ruleIndex:i,error:"Middleware: "+_e});counters.errors++;continue;}'));let A=t?",params":"",v=e?`await ae.invokeAsync(stored.actionId${A})`:`ae.invoke(stored.actionId${A})`;n.push("if(stored.actionId){"),n.push(`const ir=${v};`),n.push("counters.directivesApplied+=ir.appliedCount;counters.directivesSkipped+=ir.skippedCount;counters.errors+=ir.errors.length;"),c&&(n.push("try{"),n.push(`const _ad=${g}_hookAfter(stored.definition,ir,evalCtx);`),n.push('if(_ad==="abort")'),B(n,"false",'"afterRule"',"i+1","len",a,m),n.push("}catch(_e){}")),n.push("if(ir.aborted)"),B(n,'ir.abortedBy==="halt"',"ir.abortedBy","i+1","len",a,m),n.push("}");let p=t?"params":"undefined";n.push("if(stored.subRules&&stored.subRules.length>0){"),n.push(`if(${e?"await ":""}$.evalSub(stored.subRules,ctx,ae,counters,errors,i,${p},0,$.maxDepth))`),B(n,"false",'"sub-rule"',"i+1","len",a,m),n.push("}"),n.push("}"),n.push("const _r={success:true,aborted:false,abortedBy:undefined,matched,skipped,notMatched,errors,processedCount:len,totalCount:len,counters};"),a&&n.push(`try{${m}_hookOnComplete(_r);}catch(_e){}`),n.push("return _r;");let T=n.join(`
2
+ `),C=e?"async ":"",x=new Function("rules","ctx","ae","mw","hooks","$",`"use strict";
3
+ return(${C}()=>{
4
+ ${T}
5
+ })();`),_={maxDepth:i};return _.evalSub=e?$:M,t&&(_.runMw=D),function(k,I,f,R,E){return x(k,I,f,R,E,_)}}function P(r){if(!r.id||typeof r.id!="string")return {ruleId:r.id??"(missing)",code:"INVALID_RULE",message:"rule must have a string id"};if(typeof r.priority!="number")return {ruleId:r.id,code:"INVALID_RULE",message:"rule must have a numeric priority"};if(typeof r.when!="function")return {ruleId:r.id,code:"INVALID_RULE",message:"rule must have a when function"};let t=Array.isArray(r.then)&&r.then.length>0,e=Array.isArray(r.rules)&&r.rules.length>0;return !t&&!e?{ruleId:r.id,code:"INVALID_RULE",message:"rule must have then or sub-rules (or both)"}:null}function U(r,t){let e=`${t}.${r.id??"(missing)"}`;if(!r.id||typeof r.id!="string")return {ruleId:e,code:"INVALID_RULE",message:"sub-rule must have a string id"};let i=Array.isArray(r.then)&&r.then.length>0,o=Array.isArray(r.rules)&&r.rules.length>0;return !i&&!o?{ruleId:e,code:"INVALID_RULE",message:"sub-rule must have then or sub-rules (or both)"}:null}var N=()=>{},F=class{_actionEngine;_middleware;_ruleHooks;_isAsync;_maxSubRuleDepth;_requestedMode;_ruleHookAnalysis;_ruleEvaluator;_mode;_maybeAutoPromote;_ruleRegistry=new Map;_rules=[];_batch=null;constructor(t){if(this._actionEngine=t.actionEngine,this._middleware=t.middleware??[],this._ruleHooks=t.ruleHooks??{},this._maxSubRuleDepth=t.maxSubRuleDepth??10,this._requestedMode=t.mode??"auto",this._ruleHookAnalysis=analyzeSlots(this._ruleHooks,RULE_SLOT_NAMES),this._isAsync=this._ruleHookAnalysis.hasAnyAsync||this._actionEngine.isAsync,this._requestedMode==="jit")this._ruleEvaluator=this._buildJitRule(),this._mode="jit",this._maybeAutoPromote=N;else if(this._ruleEvaluator=O(this._ruleHookAnalysis,this._isAsync,this._maxSubRuleDepth),this._mode="interpret",this._requestedMode==="auto"){let e=0,i=t.autoJitThreshold??8;this._maybeAutoPromote=()=>{++e>=i&&this._promote();};}else this._maybeAutoPromote=N;}get actionEngine(){return this._actionEngine}get isAsync(){return this._isAsync}get compilationMode(){return this._mode}get size(){return this._rules.length}has(t){return this._ruleRegistry.has(t)}register(t){let e=[],i=[],o=[];for(let s of t){let u=P(s);if(u){i.push(u);continue}if(this._ruleRegistry.has(s.id)){i.push({ruleId:s.id,code:"DUPLICATE_ID",message:`rule "${s.id}" is already registered`});continue}let c=`rule:${s.id}`,a=Array.isArray(s.then)&&s.then.length>0,d=a?c:"",g=Array.isArray(s.rules)&&s.rules.length>0?this._collectSubRules(c,s.rules,i,o):void 0,m={definition:s,actionId:d,priority:s.priority,subRules:g?.length?g:void 0};if(this._ruleRegistry.set(s.id,m),Q(this._rules,m),a){let n=["rule"];s.tags&&n.push(...s.tags),o.push({id:d,tags:n,directives:s.then,declarations:s.declarations});}e.push(s.id);}let h=[];if(o.length>0){let s=this._actionEngine.register(o);for(let u of s.errors){let c=this._actionIdToRuleId(u.actionId);i.push({ruleId:c??u.actionId,code:u.code??"ACTION_ERROR",message:u.error});}h=s.warnings;}let b={registered:e,errors:i,warnings:h};return this._batch&&(this._batch.registered.push(...e),this._batch.errors.push(...i),this._batch.warnings.push(...h)),b}unregister(t){let e=this._ruleRegistry.get(t);return e?(this._ruleRegistry.delete(t),X(this._rules,e),e.actionId&&this._actionEngine.unregister(e.actionId),e.subRules&&this._unregisterSubRules(e.subRules),true):false}beginBatch(){this._batch||(this._batch={depth:0,registered:[],errors:[],warnings:[]}),this._batch.depth++,this._actionEngine.beginBatch();}endBatch(){if(!this._batch||this._batch.depth<=0)throw new Error("endBatch() called without matching beginBatch()");this._batch.depth--;let t=this._actionEngine.endBatch();if(this._batch.depth>0)return {registered:[],errors:[],warnings:[]};let e=this._batch;for(let o of t.errors){let h=this._actionIdToRuleId(o.actionId);e.errors.push({ruleId:h??o.actionId,code:o.code??"ACTION_ERROR",message:o.error});}e.warnings.push(...t.warnings);let i={registered:e.registered,errors:e.errors,warnings:e.warnings};return this._batch=null,i}batch(t){this.beginBatch();try{return t(this),this.endBatch()}catch(e){try{this.endBatch();}catch{}throw e}}evaluate(t){if(this._isAsync)throw new Error("Cannot call evaluate() with async hooks. Use evaluateAsync() instead.");let e=this._ruleEvaluator(this._rules,t,this._actionEngine,this._middleware,this._ruleHooks);return this._maybeAutoPromote(),e}async evaluateAsync(t){let e=await this._ruleEvaluator(this._rules,t,this._actionEngine,this._middleware,this._ruleHooks);return this._maybeAutoPromote(),e}compile(){this._requestedMode!=="interpret"&&this._mode!=="jit"&&this._promote();}_collectSubRules(t,e,i,o){let h=[],b=this._actionIdToRuleId(t);for(let s of e){let u=U(s,b);if(u){i.push(u);continue}let c=`${t}.${s.id}`,a=`${b}.${s.id}`,d=Array.isArray(s.then)&&s.then.length>0,g=Array.isArray(s.rules)&&s.rules.length>0?this._collectSubRules(c,s.rules,i,o):void 0,m={definition:s,actionId:d?c:"",subRules:g?.length?g:void 0};if(d){let n=["rule"];s.tags&&n.push(...s.tags),o.push({id:c,tags:n,directives:s.then,declarations:s.declarations});}this._ruleRegistry.set(a,m),h.push(m);}return h}_unregisterSubRules(t){for(let e of t){if(e.actionId){let i=this._actionIdToRuleId(e.actionId);i&&this._ruleRegistry.delete(i),this._actionEngine.unregister(e.actionId);}e.subRules&&this._unregisterSubRules(e.subRules);}}_actionIdToRuleId(t){return t.startsWith("rule:")?t.slice(5):null}_promote(){this._ruleEvaluator=this._buildJitRule(),this._mode="jit",this._maybeAutoPromote=N;}_buildJitRule(){return j(this._ruleHookAnalysis,this._middleware.length>0,this._isAsync,this._maxSubRuleDepth)}};function K(r){return new F(r)}function Q(r,t){let e=0,i=r.length;for(;e<i;){let o=e+i>>>1;r[o].priority>=t.priority?e=o+1:i=o;}r.splice(e,0,t);}function X(r,t){let e=r.indexOf(t);return e===-1?false:(r.splice(e,1),true)}var Y={analyze:()=>({capabilities:["halt"],dependencies:[]}),execute:()=>({ok:true,halt:true})};export{Y as HALT_HANDLER,V as createInvokerMiddleware,K as createRuleEngine};
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@statedelta-actions/rules",
3
+ "version": "0.1.0",
4
+ "description": "Rule evaluation engine with JIT-optimized sequential evaluation and sub-rules",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "dependencies": {
21
+ "@statedelta-actions/core": "0.1.0",
22
+ "@statedelta-actions/actions": "0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "apex-store": "0.2.3"
26
+ },
27
+ "author": "Anderson D. Rosa <andersondrosa@outlook.com>",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/andersondrosa/statedelta-actions.git",
32
+ "directory": "packages/rules"
33
+ },
34
+ "sideEffects": false,
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "dev": "tsup --watch",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "test:coverage": "vitest run --coverage",
47
+ "lint": "eslint src/",
48
+ "typecheck": "tsc --noEmit",
49
+ "clean": "rm -rf dist"
50
+ }
51
+ }