@statedelta-actions/events 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/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # EventProcessor — Eventos como Actions Deferidas
2
+
3
+ > **Módulo:** `events/`
4
+ > **Escopo:** Dispatcher de listeners sobre o ActionEngine. Listeners reagem a eventos nomeados, executando diretivas via hidden actions.
5
+ > **Dependências:** `core/` (tipos, slot analysis), `actions/` (IActionEngine). Zero dependência de `rules/`.
6
+ >
7
+ > Para arquitetura interna, pipelines e fluxos, ver [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md).
8
+ > Para decisões arquiteturais, ver [`docs/DECISIONS.md`](./docs/DECISIONS.md).
9
+
10
+ ---
11
+
12
+ ## O que é
13
+
14
+ O `EventProcessor` é um dispatcher de listeners sobre o ActionEngine. Quando o consumer emite `{ event: "save" }`, o processor resolve quais listeners escutam "save", monta params, e delega a execução pro ActionEngine via `invoke()`.
15
+
16
+ **Evento = action deferida.** A única diferença entre action e evento é **quando** executa. A infraestrutura é idêntica — registro, params, directives, invoke. O EventProcessor não executa diretivas ele mesmo.
17
+
18
+ **Listeners viram hidden actions.** Cada listener registrado cria uma action `event:{id}` no ActionEngine. Isso significa que listeners coexistem no mesmo grafo com actions diretas e rules (`rule:{id}`). O analyzer valida tudo junto — ciclos, capabilities, composition control.
19
+
20
+ **Sem condição, sem cascade.** Listeners não têm `when()` (match por nome, não por expressão) nem sub-rules (dispatch direto, sem recursão). Chegou evento, matched, executa.
21
+
22
+ **Halt é scoped ao evento.** Halt em um listener para apenas os listeners daquele evento. O próximo evento na fila continua normalmente.
23
+
24
+ ---
25
+
26
+ ## EventListenerDefinition — O que o consumer fornece
27
+
28
+ ```typescript
29
+ interface EventListenerDefinition<TCtx> {
30
+ readonly id: string;
31
+ readonly priority: number;
32
+ readonly on: string | string[];
33
+ readonly then: readonly Directive<TCtx>[];
34
+ readonly tags?: readonly string[];
35
+ readonly declarations?: Record<string, unknown>;
36
+ readonly metadata?: Record<string, unknown>;
37
+ }
38
+ ```
39
+
40
+ | Campo | Semântica |
41
+ |-------|-----------|
42
+ | `id` | Identificador único do listener |
43
+ | `priority` | Ordem de execução (desc — maior executa primeiro) |
44
+ | `on` | Evento(s) que o listener escuta. String ou array |
45
+ | `then` | Diretivas executadas quando matched |
46
+ | `tags` | Tags extras (além de `"event"` que é injetado automaticamente) |
47
+ | `declarations` | Contratos públicos passados pro grafo (trust boundaries) |
48
+ | `metadata` | Dados opacos armazenados na definition |
49
+
50
+ ---
51
+
52
+ ## API
53
+
54
+ ### Construction
55
+
56
+ ```typescript
57
+ import { createEventProcessor } from "@statedelta-actions/events";
58
+
59
+ const ep = createEventProcessor({
60
+ actionEngine, // IActionEngine — obrigatório
61
+ eventHooks: { ... }, // EventHooks — opcional
62
+ middleware: [ ... ], // EventMiddleware[] — opcional
63
+ mode: "auto", // "interpret" | "jit" | "auto" (default: "auto")
64
+ autoJitThreshold: 8, // Chamadas antes de auto-promote (default: 8)
65
+ });
66
+ ```
67
+
68
+ ### Registration
69
+
70
+ ```typescript
71
+ // Registrar listeners
72
+ const result = ep.register([
73
+ {
74
+ id: "on-save",
75
+ priority: 100,
76
+ on: "save",
77
+ then: [{ type: "state", target: "hp", value: 10 }],
78
+ },
79
+ {
80
+ id: "on-damage",
81
+ priority: 200,
82
+ on: ["damage", "combat"], // multi-event
83
+ then: [{ type: "state", target: "shield", value: -1 }],
84
+ },
85
+ ]);
86
+
87
+ // result: { registered: string[], errors: EventRegisterError[], warnings: RegisterWarning[] }
88
+
89
+ // Remover listener
90
+ ep.unregister("on-save"); // true se existia, false se não
91
+ ```
92
+
93
+ ### Processing
94
+
95
+ ```typescript
96
+ // Sync (throws se isAsync = true)
97
+ const result = ep.processEvents(
98
+ [{ event: "save" }, { event: "damage", data: { amount: 10 } }],
99
+ ctx,
100
+ );
101
+
102
+ // Async (sempre funciona)
103
+ const result = await ep.processEventsAsync(
104
+ [{ event: "save" }],
105
+ ctx,
106
+ );
107
+ ```
108
+
109
+ ### Batch
110
+
111
+ ```typescript
112
+ ep.beginBatch();
113
+ ep.register(listeners1);
114
+ ep.register(listeners2);
115
+ const result = ep.endBatch();
116
+ // Suporta nesting — inner endBatch é no-op, outer processa tudo
117
+ ```
118
+
119
+ ### Compilation
120
+
121
+ ```typescript
122
+ // Força promoção pra JIT (no-op se já em JIT ou mode = "interpret")
123
+ ep.compile();
124
+
125
+ // Consultar modo atual
126
+ ep.compilationMode; // "interpret" | "jit"
127
+ ep.isAsync; // true se hooks ou actionEngine são async
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Hooks — EventHooks
133
+
134
+ ```typescript
135
+ interface EventHooks<TCtx> {
136
+ beforeEvent?: (listener, evalCtx) => "skip" | "abort" | void;
137
+ afterEvent?: (listener, result, evalCtx) => "abort" | void;
138
+ onEventsComplete?: (result) => void;
139
+ }
140
+ ```
141
+
142
+ | Hook | Quando | Retornos |
143
+ |------|--------|----------|
144
+ | `beforeEvent` | Antes de cada listener | `"skip"` pula listener, `"abort"` para evento |
145
+ | `afterEvent` | Após invoke de cada listener | `"abort"` para evento |
146
+ | `onEventsComplete` | Após processar todos os eventos | fire-and-forget |
147
+
148
+ Hooks ausentes não têm custo — nem no interpreter nem no JIT.
149
+
150
+ ### Exemplo: disable via hook
151
+
152
+ ```typescript
153
+ const disabled = new Set<string>();
154
+
155
+ const ep = createEventProcessor({
156
+ actionEngine,
157
+ eventHooks: {
158
+ beforeEvent: (listener) => {
159
+ if (disabled.has(listener.id)) return "skip";
160
+ },
161
+ },
162
+ });
163
+
164
+ disabled.add("on-save"); // "disable"
165
+ disabled.delete("on-save"); // "enable"
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Middleware
171
+
172
+ ```typescript
173
+ type EventMiddleware<TCtx> = (
174
+ listener: EventListenerDefinition<TCtx>,
175
+ ctx: TCtx,
176
+ params: Record<string, unknown>,
177
+ ) => Record<string, unknown>;
178
+ ```
179
+
180
+ Middleware é pipeline de **enriquecimento de dados**. Não afeta fluxo de controle (sem "skip", sem "abort"). Cada middleware recebe e retorna params.
181
+
182
+ ```typescript
183
+ const ep = createEventProcessor({
184
+ actionEngine,
185
+ middleware: [
186
+ (listener, ctx, params) => ({
187
+ ...params,
188
+ timestamp: Date.now(),
189
+ source: listener.id,
190
+ }),
191
+ ],
192
+ });
193
+ ```
194
+
195
+ Sem middleware, `$event` chega direto no scope da action. Com middleware, `$event` entra como base e o middleware enriquece.
196
+
197
+ ---
198
+
199
+ ## $event Injection
200
+
201
+ Todo listener recebe automaticamente no scope:
202
+
203
+ ```typescript
204
+ { $event: { type: "save", data: eventData } }
205
+ ```
206
+
207
+ Acessível dentro de handlers via `frame.scope.$event`.
208
+
209
+ ---
210
+
211
+ ## Results
212
+
213
+ ```typescript
214
+ interface EventProcessingResult {
215
+ readonly eventResults: readonly SingleEventResult[];
216
+ readonly unprocessedEvents: readonly string[]; // eventos sem listeners
217
+ readonly errors: readonly RuleError[];
218
+ readonly counters: FrameCounters;
219
+ readonly totalEvents: number;
220
+ readonly processedEvents: number;
221
+ }
222
+
223
+ interface SingleEventResult {
224
+ readonly event: string;
225
+ readonly matched: readonly number[];
226
+ readonly skipped: readonly number[];
227
+ readonly notMatched: readonly number[];
228
+ readonly errors: readonly RuleError[];
229
+ readonly halted: boolean;
230
+ readonly haltedBy?: string;
231
+ readonly counters: FrameCounters;
232
+ }
233
+
234
+ interface EventRegisterResult {
235
+ readonly registered: readonly string[];
236
+ readonly errors: readonly EventRegisterError[];
237
+ readonly warnings: readonly RegisterWarning[];
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Compilation — 3 Modos
244
+
245
+ | Modo | Comportamento |
246
+ |------|--------------|
247
+ | `"interpret"` | Interpreter sempre. Sem promoção |
248
+ | `"jit"` | JIT imediato no constructor |
249
+ | `"auto"` (default) | Interpreter inicial. Promove pra JIT após `autoJitThreshold` chamadas |
250
+
251
+ O JIT gera código equivalente ao interpreter via `new Function()`, eliminando branches de hooks/middleware ausentes em compile-time. O consumer não precisa se preocupar — `"auto"` é o default e promove transparentemente.
package/dist/index.cjs ADDED
@@ -0,0 +1,5 @@
1
+ 'use strict';var core=require('@statedelta-actions/core');function L(){return {rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0}}function O(e,s){e.rulesEvaluated+=s.rulesEvaluated,e.rulesMatched+=s.rulesMatched,e.rulesSkipped+=s.rulesSkipped,e.directivesApplied+=s.directivesApplied,e.directivesSkipped+=s.directivesSkipped,e.subRunsCreated+=s.subRunsCreated,e.errors+=s.errors;}function M(e,s,r,i){let o=i?Object.assign({},i):{};for(let d of e)Object.assign(o,d(s,r,o));return o}function q(e){let s=e.filledNames.has("beforeEvent"),r=e.filledNames.has("afterEvent"),i=e.filledNames.has("onEventsComplete");return function(d,g,n,a,u,m){let _=[],b=[],t=[],l=L(),h=u.length>0;a.setContext(n);for(let p of g){let y=d.get(p.event);if(!y||y.length===0){b.push(p.event);continue}let x=[],R=[],S=[],C=[],c=L(),k=false,T,D={ctx:n,counters:c,event:p.event},N={$event:{type:p.event,data:p.data}};for(let v=0;v<y.length;v++){let I=y[v];if(c.rulesEvaluated++,s)try{let f=m.beforeEvent(I.definition,D);if(f==="skip"){R.push(v),c.rulesSkipped++;continue}if(f==="abort")break}catch(f){C.push({ruleIndex:v,error:`beforeEvent: ${f}`}),c.errors++;}x.push(v),c.rulesMatched++;let w;if(h)try{w=M(u,I.definition,n,N);}catch(f){C.push({ruleIndex:v,error:`Middleware: ${f}`}),c.errors++;continue}else w=N;let E=a.invoke(I.actionId,w);if(c.directivesApplied+=E.appliedCount,c.directivesSkipped+=E.skippedCount,c.errors+=E.errors.length,r)try{if(m.afterEvent(I.definition,E,D)==="abort")break}catch{}if(E.aborted){k=true,T=E.abortedBy;break}}t.push(...C),O(l,c),_.push({event:p.event,matched:x,skipped:R,notMatched:S,errors:C,halted:k,haltedBy:T,counters:c});}let A={eventResults:_,unprocessedEvents:b,errors:t,counters:l,totalEvents:g.length,processedEvents:_.length};if(i)try{m.onEventsComplete(A);}catch{}return A}}function Q(e){let s=e.filledNames.has("beforeEvent"),r=e.filledNames.has("afterEvent"),i=e.filledNames.has("onEventsComplete");return async function(d,g,n,a,u,m){let _=[],b=[],t=[],l=L(),h=u.length>0;a.setContext(n);for(let p of g){let y=d.get(p.event);if(!y||y.length===0){b.push(p.event);continue}let x=[],R=[],S=[],C=[],c=L(),k=false,T,D={ctx:n,counters:c,event:p.event},N={$event:{type:p.event,data:p.data}};for(let v=0;v<y.length;v++){let I=y[v];if(c.rulesEvaluated++,s)try{let f=await m.beforeEvent(I.definition,D);if(f==="skip"){R.push(v),c.rulesSkipped++;continue}if(f==="abort")break}catch(f){C.push({ruleIndex:v,error:`beforeEvent: ${f}`}),c.errors++;}x.push(v),c.rulesMatched++;let w;if(h)try{w=M(u,I.definition,n,N);}catch(f){C.push({ruleIndex:v,error:`Middleware: ${f}`}),c.errors++;continue}else w=N;let E=await a.invokeAsync(I.actionId,w);if(c.directivesApplied+=E.appliedCount,c.directivesSkipped+=E.skippedCount,c.errors+=E.errors.length,r)try{if(await m.afterEvent(I.definition,E,D)==="abort")break}catch{}if(E.aborted){k=true,T=E.abortedBy;break}}t.push(...C),O(l,c),_.push({event:p.event,matched:x,skipped:R,notMatched:S,errors:C,halted:k,haltedBy:T,counters:c});}let A={eventResults:_,unprocessedEvents:b,errors:t,counters:l,totalEvents:g.length,processedEvents:_.length};if(i)try{await m.onEventsComplete(A);}catch{}return A}}function B(e,s){return s?Q(e):q(e)}function F(e,s,r){let{filledNames:i,asyncNames:o}=e,d=R=>i.has(R),g=R=>o.has(R)?"await ":"",n=d("beforeEvent"),a=d("afterEvent"),u=d("onEventsComplete"),m=g("beforeEvent"),_=g("afterEvent"),b=g("onEventsComplete"),t=[];t.push("const eventResults=[];"),t.push("const unprocessedEvents=[];"),t.push("const aggErrors=[];"),t.push("const aggCounters={rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0};"),t.push("ae.setContext(ctx);"),u&&t.push("const _hookOnComplete=hooks.onEventsComplete;"),t.push("for(let _ei=0;_ei<events.length;_ei++){"),t.push("const ev=events[_ei];"),t.push("const _evListeners=listenerIndex.get(ev.event);"),t.push("if(!_evListeners||_evListeners.length===0){unprocessedEvents.push(ev.event);continue;}"),t.push("const matched=[];const skipped=[];const notMatched=[];"),t.push("const errors=[];"),t.push("const counters={rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0};"),t.push("let halted=false;let haltedBy;"),(n||a)&&t.push("const evalCtx={ctx,counters,event:ev.event};"),n&&t.push("const _hookBefore=hooks.beforeEvent;"),a&&t.push("const _hookAfter=hooks.afterEvent;"),t.push("const _ep={$event:{type:ev.event,data:ev.data}};"),t.push("for(let _ri=0;_ri<_evListeners.length;_ri++){"),t.push("const stored=_evListeners[_ri];"),t.push("counters.rulesEvaluated++;"),n&&(t.push("try{"),t.push(`const _bd=${m}_hookBefore(stored.definition,evalCtx);`),t.push('if(_bd==="skip"){skipped.push(_ri);counters.rulesSkipped++;continue;}'),t.push('if(_bd==="abort"){break;}'),t.push('}catch(_e){errors.push({ruleIndex:_ri,error:"beforeEvent: "+_e});counters.errors++;}')),t.push("matched.push(_ri);counters.rulesMatched++;"),s&&(t.push("let params;"),t.push('try{params=$.runMw(mw,stored.definition,ctx,_ep);}catch(_e){errors.push({ruleIndex:_ri,error:"Middleware: "+_e});counters.errors++;continue;}'));let l=s?"params":"_ep",h=r?`await ae.invokeAsync(stored.actionId,${l})`:`ae.invoke(stored.actionId,${l})`;t.push(`const ir=${h};`),t.push("counters.directivesApplied+=ir.appliedCount;counters.directivesSkipped+=ir.skippedCount;counters.errors+=ir.errors.length;"),a&&(t.push("try{"),t.push(`const _ad=${_}_hookAfter(stored.definition,ir,evalCtx);`),t.push('if(_ad==="abort"){break;}'),t.push("}catch(_e){}")),t.push("if(ir.aborted){halted=true;haltedBy=ir.abortedBy;break;}"),t.push("}"),t.push("aggCounters.rulesEvaluated+=counters.rulesEvaluated;"),t.push("aggCounters.rulesMatched+=counters.rulesMatched;"),t.push("aggCounters.rulesSkipped+=counters.rulesSkipped;"),t.push("aggCounters.directivesApplied+=counters.directivesApplied;"),t.push("aggCounters.directivesSkipped+=counters.directivesSkipped;"),t.push("aggCounters.subRunsCreated+=counters.subRunsCreated;"),t.push("aggCounters.errors+=counters.errors;"),t.push("for(let _k=0;_k<errors.length;_k++)aggErrors.push(errors[_k]);"),t.push("eventResults.push({event:ev.event,matched,skipped,notMatched,errors,halted,haltedBy,counters});"),t.push("}"),t.push("const _r={eventResults,unprocessedEvents,errors:aggErrors,counters:aggCounters,totalEvents:events.length,processedEvents:eventResults.length};"),u&&t.push(`try{${b}_hookOnComplete(_r);}catch(_e){}`),t.push("return _r;");let A=t.join(`
2
+ `),p=r?"async ":"",y=new Function("listenerIndex","events","ctx","ae","mw","hooks","$",`"use strict";
3
+ return(${p}()=>{
4
+ ${A}
5
+ })();`),x={};return s&&(x.runMw=M),function(S,C,c,k,T,D){return y(S,C,c,k,T,D,x)}}function V(e){return !e.event||typeof e.event!="string"?{event:e.event??"(missing)",code:"INVALID_DEFINITION",message:"event definition must have a non-empty string event name"}:e.tags!==void 0&&!Array.isArray(e.tags)?{event:e.event,code:"INVALID_DEFINITION",message:"event definition tags must be an array of strings"}:e.tier!==void 0&&typeof e.tier!="number"?{event:e.event,code:"INVALID_DEFINITION",message:"event definition tier must be a number"}:null}function j(e){if(!e.id||typeof e.id!="string")return {listenerId:e.id??"(missing)",code:"INVALID_LISTENER",message:"listener must have a string id"};if(typeof e.priority!="number")return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener must have a numeric priority"};if(typeof e.on=="string"){if(!e.on)return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener.on must be a non-empty string or string[]"}}else if(Array.isArray(e.on)){if(e.on.length===0||e.on.some(s=>typeof s!="string"||!s))return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener.on must be a non-empty string or string[] of non-empty strings"}}else return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener.on must be a string or string[]"};return !Array.isArray(e.then)||e.then.length===0?{listenerId:e.id,code:"INVALID_LISTENER",message:"listener must have a non-empty then array"}:null}function H(e,s){let r=0,i=e.length;for(;r<i;){let o=r+i>>>1;e[o].priority>=s.priority?r=o+1:i=o;}e.splice(r,0,s);}function W(e,s){let r=e.indexOf(s);return r===-1?false:(e.splice(r,1),true)}var P=()=>{},$=class{_actionEngine;_middleware;_eventHooks;_isAsync;_requestedMode;_hookAnalysis;_dispatcher;_mode;_maybeAutoPromote;_eventDefinitions=new Map;_listenerRegistry=new Map;_listenerIndex=new Map;_batch=null;constructor(s){if(this._actionEngine=s.actionEngine,this._middleware=s.middleware??[],this._eventHooks=s.eventHooks??{},this._requestedMode=s.mode??"auto",this._hookAnalysis=core.analyzeSlots(this._eventHooks,core.EVENT_SLOT_NAMES),this._isAsync=this._hookAnalysis.hasAnyAsync||this._actionEngine.isAsync,this._requestedMode==="jit")this._dispatcher=this._buildJit(),this._mode="jit",this._maybeAutoPromote=P;else if(this._dispatcher=B(this._hookAnalysis,this._isAsync),this._mode="interpret",this._requestedMode==="auto"){let r=0,i=s.autoJitThreshold??8;this._maybeAutoPromote=()=>{++r>=i&&this._promote();};}else this._maybeAutoPromote=P;}get actionEngine(){return this._actionEngine}get isAsync(){return this._isAsync}get compilationMode(){return this._mode}get eventDefinitions(){return this._eventDefinitions}getEventDefinition(s){return this._eventDefinitions.get(s)}defineEvents(s){let r=[],i=[],o=[];for(let n of s){let a=V(n);if(a){i.push(a);continue}if(this._eventDefinitions.has(n.event)){i.push({event:n.event,code:"DUPLICATE_EVENT",message:`event definition "${n.event}" is already registered`});continue}this._eventDefinitions.set(n.event,n);let u=["eventdef"];n.tags&&u.push(...n.tags),o.push({id:`eventdef:${n.event}`,directives:[],tags:u,declarations:n.declarations,tier:n.tier,metadata:{...n.schema!==void 0&&{schema:n.schema},...n.description!==void 0&&{description:n.description}}}),r.push(n.event);}let d=[];if(o.length>0){let n=this._actionEngine.register(o);for(let a of n.errors){let u=this._actionIdToEventName(a.actionId);i.push({event:u??a.actionId,code:a.code??"ACTION_ERROR",message:a.error});}d=n.warnings;}let g={defined:r,errors:i,warnings:d};return this._batch&&(this._batch.definedEvents.push(...r),this._batch.defineErrors.push(...i),this._batch.warnings.push(...d)),g}register(s){let r=[],i=[],o=[],d=[];for(let n of s){let a=j(n);if(a){i.push(a);continue}if(this._listenerRegistry.has(n.id)){i.push({listenerId:n.id,code:"DUPLICATE_ID",message:`listener "${n.id}" is already registered`});continue}let u=typeof n.on=="string"?[n.on]:n.on,m=false;for(let l of u){let h=this._eventDefinitions.get(l);if(!h){o.push({actionId:`event:${n.id}`,code:"NO_EVENT_DEFINITION",message:`listener "${n.id}" listens to "${l}" but no event definition is registered for it`});continue}h.tier!==void 0&&n.declarations?.tier!==void 0&&n.declarations.tier<h.tier&&(i.push({listenerId:n.id,code:"TIER_VIOLATION",message:`listener "${n.id}" declares tier ${n.declarations.tier} but event "${l}" requires tier >= ${h.tier}`}),m=true);}if(m)continue;let _=`event:${n.id}`,b={definition:n,actionId:_,priority:n.priority};this._listenerRegistry.set(n.id,b);for(let l of u){let h=this._listenerIndex.get(l);h||(h=[],this._listenerIndex.set(l,h)),H(h,b);}let t=["event"];n.tags&&t.push(...n.tags),d.push({id:_,tags:t,directives:n.then,declarations:n.declarations}),r.push(n.id);}if(d.length>0){let n=this._actionEngine.register(d);for(let a of n.errors){let u=this._actionIdToListenerId(a.actionId);i.push({listenerId:u??a.actionId,code:a.code??"ACTION_ERROR",message:a.error});}o.push(...n.warnings);}let g={registered:r,errors:i,warnings:o};return this._batch&&(this._batch.registered.push(...r),this._batch.errors.push(...i),this._batch.warnings.push(...o)),g}unregister(s){let r=this._listenerRegistry.get(s);if(!r)return false;this._listenerRegistry.delete(s);let i=typeof r.definition.on=="string"?[r.definition.on]:r.definition.on;for(let o of i){let d=this._listenerIndex.get(o);d&&(W(d,r),d.length===0&&this._listenerIndex.delete(o));}return this._actionEngine.unregister(r.actionId),true}processEvents(s,r){if(this._isAsync)throw new Error("Cannot call processEvents() with async hooks. Use processEventsAsync() instead.");let i=this._dispatcher(this._listenerIndex,s,r,this._actionEngine,this._middleware,this._eventHooks);return this._maybeAutoPromote(),i}async processEventsAsync(s,r){let i=await this._dispatcher(this._listenerIndex,s,r,this._actionEngine,this._middleware,this._eventHooks);return this._maybeAutoPromote(),i}beginBatch(){this._batch||(this._batch={depth:0,registered:[],errors:[],warnings:[],definedEvents:[],defineErrors:[]}),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 s=this._actionEngine.endBatch();if(this._batch.depth>0)return {registered:[],errors:[],warnings:[]};let r=this._batch;for(let o of s.errors){let d=this._actionIdToListenerId(o.actionId);r.errors.push({listenerId:d??o.actionId,code:o.code??"ACTION_ERROR",message:o.error});}r.warnings.push(...s.warnings);let i={registered:r.registered,errors:r.errors,warnings:r.warnings};return this._batch=null,i}compile(){this._requestedMode!=="interpret"&&this._mode!=="jit"&&this._promote();}_promote(){this._dispatcher=this._buildJit(),this._mode="jit",this._maybeAutoPromote=P;}_buildJit(){return F(this._hookAnalysis,this._middleware.length>0,this._isAsync)}_actionIdToListenerId(s){return s.startsWith("event:")?s.slice(6):null}_actionIdToEventName(s){return s.startsWith("eventdef:")?s.slice(9):null}};function z(e){return new $(e)}function re(e="event"){return s=>{let r=s[e],i=typeof r=="string"&&r?[`eventdef:${r}`]:[];return {capabilities:["emit"],dependencies:i}}}exports.createEmitAnalyzer=re;exports.createEventProcessor=z;
@@ -0,0 +1,128 @@
1
+ import { HookFn, Directive, FrameCounters, DirectiveResult, RuleError } from '@statedelta-actions/core';
2
+ import { RegisterWarning, IActionEngine, HandlerAnalysis } from '@statedelta-actions/actions';
3
+
4
+ interface EventDefinition {
5
+ /** Nome do evento. Chave única no registry. */
6
+ readonly event: string;
7
+ /** Schema do payload (shape dos dados que emit envia e listeners recebem). */
8
+ readonly schema?: Record<string, unknown>;
9
+ /** Tags para categorização e composition control. */
10
+ readonly tags?: readonly string[];
11
+ /** Tier do evento (validação hierárquica). */
12
+ readonly tier?: number;
13
+ /** Declarations (trust boundary — o que dependents veem). */
14
+ readonly declarations?: Record<string, unknown>;
15
+ /** Descrição humana. */
16
+ readonly description?: string;
17
+ }
18
+ interface EventDefineError {
19
+ readonly event: string;
20
+ readonly code: string;
21
+ readonly message: string;
22
+ }
23
+ interface EventDefineResult {
24
+ readonly defined: readonly string[];
25
+ readonly errors: readonly EventDefineError[];
26
+ readonly warnings: readonly RegisterWarning[];
27
+ }
28
+ interface EventListenerDefinition<TCtx = unknown> {
29
+ readonly id: string;
30
+ readonly priority: number;
31
+ readonly on: string | string[];
32
+ readonly then: readonly Directive<TCtx>[];
33
+ readonly tags?: readonly string[];
34
+ readonly declarations?: Record<string, unknown>;
35
+ readonly metadata?: Record<string, unknown>;
36
+ }
37
+ interface EventProcessingContext<TCtx = unknown> {
38
+ readonly ctx: TCtx;
39
+ readonly counters: FrameCounters;
40
+ readonly event: string;
41
+ }
42
+ interface SingleEventResult {
43
+ readonly event: string;
44
+ readonly matched: readonly number[];
45
+ readonly skipped: readonly number[];
46
+ readonly notMatched: readonly number[];
47
+ readonly errors: readonly RuleError[];
48
+ readonly halted: boolean;
49
+ readonly haltedBy?: string;
50
+ readonly counters: FrameCounters;
51
+ }
52
+ interface EventProcessingResult {
53
+ readonly eventResults: readonly SingleEventResult[];
54
+ readonly unprocessedEvents: readonly string[];
55
+ readonly errors: readonly RuleError[];
56
+ readonly counters: FrameCounters;
57
+ readonly totalEvents: number;
58
+ readonly processedEvents: number;
59
+ }
60
+ interface EventHooks<TCtx = unknown> {
61
+ beforeEvent?: HookFn<[
62
+ listener: EventListenerDefinition<TCtx>,
63
+ evalCtx: EventProcessingContext<TCtx>
64
+ ], "skip" | "abort" | void>;
65
+ afterEvent?: HookFn<[
66
+ listener: EventListenerDefinition<TCtx>,
67
+ result: DirectiveResult,
68
+ evalCtx: EventProcessingContext<TCtx>
69
+ ], "abort" | void>;
70
+ onEventsComplete?: HookFn<[result: EventProcessingResult], void>;
71
+ }
72
+ interface QueuedEvent {
73
+ readonly event: string;
74
+ readonly data?: unknown;
75
+ }
76
+ interface EventRegisterError {
77
+ readonly listenerId: string;
78
+ readonly code: string;
79
+ readonly message: string;
80
+ }
81
+ interface EventRegisterResult {
82
+ readonly registered: readonly string[];
83
+ readonly errors: readonly EventRegisterError[];
84
+ readonly warnings: readonly RegisterWarning[];
85
+ }
86
+ type EventMiddleware<TCtx = unknown> = (listener: EventListenerDefinition<TCtx>, ctx: TCtx, params: Record<string, unknown>) => Record<string, unknown>;
87
+ interface EventProcessorConfig<TCtx = unknown> {
88
+ actionEngine: IActionEngine<TCtx>;
89
+ middleware?: readonly EventMiddleware<TCtx>[];
90
+ eventHooks?: EventHooks<TCtx>;
91
+ mode?: "interpret" | "jit" | "auto";
92
+ autoJitThreshold?: number;
93
+ }
94
+ interface IEventProcessor<TCtx = unknown> {
95
+ defineEvents(definitions: readonly EventDefinition[]): EventDefineResult;
96
+ getEventDefinition(event: string): EventDefinition | undefined;
97
+ readonly eventDefinitions: ReadonlyMap<string, EventDefinition>;
98
+ register(listeners: readonly EventListenerDefinition<TCtx>[]): EventRegisterResult;
99
+ unregister(id: string): boolean;
100
+ processEvents(events: readonly QueuedEvent[], ctx: TCtx): EventProcessingResult;
101
+ processEventsAsync(events: readonly QueuedEvent[], ctx: TCtx): Promise<EventProcessingResult>;
102
+ beginBatch(): void;
103
+ endBatch(): EventRegisterResult;
104
+ compile(): void;
105
+ readonly actionEngine: IActionEngine<TCtx>;
106
+ readonly isAsync: boolean;
107
+ readonly compilationMode: "interpret" | "jit";
108
+ }
109
+
110
+ declare function createEventProcessor<TCtx>(config: EventProcessorConfig<TCtx>): IEventProcessor<TCtx>;
111
+
112
+ /**
113
+ * Cria uma função `analyze()` para o handler `emit`.
114
+ * Retorna capabilities: ["emit"] e dependency para `eventdef:{event}`.
115
+ *
116
+ * Uso no consumer:
117
+ * ```ts
118
+ * const handlers = {
119
+ * emit: {
120
+ * execute: (d, frame) => { ... },
121
+ * analyze: createEmitAnalyzer(),
122
+ * },
123
+ * };
124
+ * ```
125
+ */
126
+ declare function createEmitAnalyzer(eventField?: string): (directive: Directive) => HandlerAnalysis;
127
+
128
+ export { type EventDefineError, type EventDefineResult, type EventDefinition, type EventHooks, type EventListenerDefinition, type EventMiddleware, type EventProcessingContext, type EventProcessingResult, type EventProcessorConfig, type EventRegisterError, type EventRegisterResult, type IEventProcessor, type QueuedEvent, type SingleEventResult, createEmitAnalyzer, createEventProcessor };
@@ -0,0 +1,128 @@
1
+ import { HookFn, Directive, FrameCounters, DirectiveResult, RuleError } from '@statedelta-actions/core';
2
+ import { RegisterWarning, IActionEngine, HandlerAnalysis } from '@statedelta-actions/actions';
3
+
4
+ interface EventDefinition {
5
+ /** Nome do evento. Chave única no registry. */
6
+ readonly event: string;
7
+ /** Schema do payload (shape dos dados que emit envia e listeners recebem). */
8
+ readonly schema?: Record<string, unknown>;
9
+ /** Tags para categorização e composition control. */
10
+ readonly tags?: readonly string[];
11
+ /** Tier do evento (validação hierárquica). */
12
+ readonly tier?: number;
13
+ /** Declarations (trust boundary — o que dependents veem). */
14
+ readonly declarations?: Record<string, unknown>;
15
+ /** Descrição humana. */
16
+ readonly description?: string;
17
+ }
18
+ interface EventDefineError {
19
+ readonly event: string;
20
+ readonly code: string;
21
+ readonly message: string;
22
+ }
23
+ interface EventDefineResult {
24
+ readonly defined: readonly string[];
25
+ readonly errors: readonly EventDefineError[];
26
+ readonly warnings: readonly RegisterWarning[];
27
+ }
28
+ interface EventListenerDefinition<TCtx = unknown> {
29
+ readonly id: string;
30
+ readonly priority: number;
31
+ readonly on: string | string[];
32
+ readonly then: readonly Directive<TCtx>[];
33
+ readonly tags?: readonly string[];
34
+ readonly declarations?: Record<string, unknown>;
35
+ readonly metadata?: Record<string, unknown>;
36
+ }
37
+ interface EventProcessingContext<TCtx = unknown> {
38
+ readonly ctx: TCtx;
39
+ readonly counters: FrameCounters;
40
+ readonly event: string;
41
+ }
42
+ interface SingleEventResult {
43
+ readonly event: string;
44
+ readonly matched: readonly number[];
45
+ readonly skipped: readonly number[];
46
+ readonly notMatched: readonly number[];
47
+ readonly errors: readonly RuleError[];
48
+ readonly halted: boolean;
49
+ readonly haltedBy?: string;
50
+ readonly counters: FrameCounters;
51
+ }
52
+ interface EventProcessingResult {
53
+ readonly eventResults: readonly SingleEventResult[];
54
+ readonly unprocessedEvents: readonly string[];
55
+ readonly errors: readonly RuleError[];
56
+ readonly counters: FrameCounters;
57
+ readonly totalEvents: number;
58
+ readonly processedEvents: number;
59
+ }
60
+ interface EventHooks<TCtx = unknown> {
61
+ beforeEvent?: HookFn<[
62
+ listener: EventListenerDefinition<TCtx>,
63
+ evalCtx: EventProcessingContext<TCtx>
64
+ ], "skip" | "abort" | void>;
65
+ afterEvent?: HookFn<[
66
+ listener: EventListenerDefinition<TCtx>,
67
+ result: DirectiveResult,
68
+ evalCtx: EventProcessingContext<TCtx>
69
+ ], "abort" | void>;
70
+ onEventsComplete?: HookFn<[result: EventProcessingResult], void>;
71
+ }
72
+ interface QueuedEvent {
73
+ readonly event: string;
74
+ readonly data?: unknown;
75
+ }
76
+ interface EventRegisterError {
77
+ readonly listenerId: string;
78
+ readonly code: string;
79
+ readonly message: string;
80
+ }
81
+ interface EventRegisterResult {
82
+ readonly registered: readonly string[];
83
+ readonly errors: readonly EventRegisterError[];
84
+ readonly warnings: readonly RegisterWarning[];
85
+ }
86
+ type EventMiddleware<TCtx = unknown> = (listener: EventListenerDefinition<TCtx>, ctx: TCtx, params: Record<string, unknown>) => Record<string, unknown>;
87
+ interface EventProcessorConfig<TCtx = unknown> {
88
+ actionEngine: IActionEngine<TCtx>;
89
+ middleware?: readonly EventMiddleware<TCtx>[];
90
+ eventHooks?: EventHooks<TCtx>;
91
+ mode?: "interpret" | "jit" | "auto";
92
+ autoJitThreshold?: number;
93
+ }
94
+ interface IEventProcessor<TCtx = unknown> {
95
+ defineEvents(definitions: readonly EventDefinition[]): EventDefineResult;
96
+ getEventDefinition(event: string): EventDefinition | undefined;
97
+ readonly eventDefinitions: ReadonlyMap<string, EventDefinition>;
98
+ register(listeners: readonly EventListenerDefinition<TCtx>[]): EventRegisterResult;
99
+ unregister(id: string): boolean;
100
+ processEvents(events: readonly QueuedEvent[], ctx: TCtx): EventProcessingResult;
101
+ processEventsAsync(events: readonly QueuedEvent[], ctx: TCtx): Promise<EventProcessingResult>;
102
+ beginBatch(): void;
103
+ endBatch(): EventRegisterResult;
104
+ compile(): void;
105
+ readonly actionEngine: IActionEngine<TCtx>;
106
+ readonly isAsync: boolean;
107
+ readonly compilationMode: "interpret" | "jit";
108
+ }
109
+
110
+ declare function createEventProcessor<TCtx>(config: EventProcessorConfig<TCtx>): IEventProcessor<TCtx>;
111
+
112
+ /**
113
+ * Cria uma função `analyze()` para o handler `emit`.
114
+ * Retorna capabilities: ["emit"] e dependency para `eventdef:{event}`.
115
+ *
116
+ * Uso no consumer:
117
+ * ```ts
118
+ * const handlers = {
119
+ * emit: {
120
+ * execute: (d, frame) => { ... },
121
+ * analyze: createEmitAnalyzer(),
122
+ * },
123
+ * };
124
+ * ```
125
+ */
126
+ declare function createEmitAnalyzer(eventField?: string): (directive: Directive) => HandlerAnalysis;
127
+
128
+ export { type EventDefineError, type EventDefineResult, type EventDefinition, type EventHooks, type EventListenerDefinition, type EventMiddleware, type EventProcessingContext, type EventProcessingResult, type EventProcessorConfig, type EventRegisterError, type EventRegisterResult, type IEventProcessor, type QueuedEvent, type SingleEventResult, createEmitAnalyzer, createEventProcessor };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import {analyzeSlots,EVENT_SLOT_NAMES}from'@statedelta-actions/core';function L(){return {rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0}}function O(e,s){e.rulesEvaluated+=s.rulesEvaluated,e.rulesMatched+=s.rulesMatched,e.rulesSkipped+=s.rulesSkipped,e.directivesApplied+=s.directivesApplied,e.directivesSkipped+=s.directivesSkipped,e.subRunsCreated+=s.subRunsCreated,e.errors+=s.errors;}function M(e,s,r,i){let o=i?Object.assign({},i):{};for(let d of e)Object.assign(o,d(s,r,o));return o}function q(e){let s=e.filledNames.has("beforeEvent"),r=e.filledNames.has("afterEvent"),i=e.filledNames.has("onEventsComplete");return function(d,g,n,a,u,m){let _=[],b=[],t=[],l=L(),h=u.length>0;a.setContext(n);for(let p of g){let y=d.get(p.event);if(!y||y.length===0){b.push(p.event);continue}let x=[],R=[],S=[],C=[],c=L(),k=false,T,D={ctx:n,counters:c,event:p.event},N={$event:{type:p.event,data:p.data}};for(let v=0;v<y.length;v++){let I=y[v];if(c.rulesEvaluated++,s)try{let f=m.beforeEvent(I.definition,D);if(f==="skip"){R.push(v),c.rulesSkipped++;continue}if(f==="abort")break}catch(f){C.push({ruleIndex:v,error:`beforeEvent: ${f}`}),c.errors++;}x.push(v),c.rulesMatched++;let w;if(h)try{w=M(u,I.definition,n,N);}catch(f){C.push({ruleIndex:v,error:`Middleware: ${f}`}),c.errors++;continue}else w=N;let E=a.invoke(I.actionId,w);if(c.directivesApplied+=E.appliedCount,c.directivesSkipped+=E.skippedCount,c.errors+=E.errors.length,r)try{if(m.afterEvent(I.definition,E,D)==="abort")break}catch{}if(E.aborted){k=true,T=E.abortedBy;break}}t.push(...C),O(l,c),_.push({event:p.event,matched:x,skipped:R,notMatched:S,errors:C,halted:k,haltedBy:T,counters:c});}let A={eventResults:_,unprocessedEvents:b,errors:t,counters:l,totalEvents:g.length,processedEvents:_.length};if(i)try{m.onEventsComplete(A);}catch{}return A}}function Q(e){let s=e.filledNames.has("beforeEvent"),r=e.filledNames.has("afterEvent"),i=e.filledNames.has("onEventsComplete");return async function(d,g,n,a,u,m){let _=[],b=[],t=[],l=L(),h=u.length>0;a.setContext(n);for(let p of g){let y=d.get(p.event);if(!y||y.length===0){b.push(p.event);continue}let x=[],R=[],S=[],C=[],c=L(),k=false,T,D={ctx:n,counters:c,event:p.event},N={$event:{type:p.event,data:p.data}};for(let v=0;v<y.length;v++){let I=y[v];if(c.rulesEvaluated++,s)try{let f=await m.beforeEvent(I.definition,D);if(f==="skip"){R.push(v),c.rulesSkipped++;continue}if(f==="abort")break}catch(f){C.push({ruleIndex:v,error:`beforeEvent: ${f}`}),c.errors++;}x.push(v),c.rulesMatched++;let w;if(h)try{w=M(u,I.definition,n,N);}catch(f){C.push({ruleIndex:v,error:`Middleware: ${f}`}),c.errors++;continue}else w=N;let E=await a.invokeAsync(I.actionId,w);if(c.directivesApplied+=E.appliedCount,c.directivesSkipped+=E.skippedCount,c.errors+=E.errors.length,r)try{if(await m.afterEvent(I.definition,E,D)==="abort")break}catch{}if(E.aborted){k=true,T=E.abortedBy;break}}t.push(...C),O(l,c),_.push({event:p.event,matched:x,skipped:R,notMatched:S,errors:C,halted:k,haltedBy:T,counters:c});}let A={eventResults:_,unprocessedEvents:b,errors:t,counters:l,totalEvents:g.length,processedEvents:_.length};if(i)try{await m.onEventsComplete(A);}catch{}return A}}function B(e,s){return s?Q(e):q(e)}function F(e,s,r){let{filledNames:i,asyncNames:o}=e,d=R=>i.has(R),g=R=>o.has(R)?"await ":"",n=d("beforeEvent"),a=d("afterEvent"),u=d("onEventsComplete"),m=g("beforeEvent"),_=g("afterEvent"),b=g("onEventsComplete"),t=[];t.push("const eventResults=[];"),t.push("const unprocessedEvents=[];"),t.push("const aggErrors=[];"),t.push("const aggCounters={rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0};"),t.push("ae.setContext(ctx);"),u&&t.push("const _hookOnComplete=hooks.onEventsComplete;"),t.push("for(let _ei=0;_ei<events.length;_ei++){"),t.push("const ev=events[_ei];"),t.push("const _evListeners=listenerIndex.get(ev.event);"),t.push("if(!_evListeners||_evListeners.length===0){unprocessedEvents.push(ev.event);continue;}"),t.push("const matched=[];const skipped=[];const notMatched=[];"),t.push("const errors=[];"),t.push("const counters={rulesEvaluated:0,rulesMatched:0,rulesSkipped:0,directivesApplied:0,directivesSkipped:0,subRunsCreated:0,errors:0};"),t.push("let halted=false;let haltedBy;"),(n||a)&&t.push("const evalCtx={ctx,counters,event:ev.event};"),n&&t.push("const _hookBefore=hooks.beforeEvent;"),a&&t.push("const _hookAfter=hooks.afterEvent;"),t.push("const _ep={$event:{type:ev.event,data:ev.data}};"),t.push("for(let _ri=0;_ri<_evListeners.length;_ri++){"),t.push("const stored=_evListeners[_ri];"),t.push("counters.rulesEvaluated++;"),n&&(t.push("try{"),t.push(`const _bd=${m}_hookBefore(stored.definition,evalCtx);`),t.push('if(_bd==="skip"){skipped.push(_ri);counters.rulesSkipped++;continue;}'),t.push('if(_bd==="abort"){break;}'),t.push('}catch(_e){errors.push({ruleIndex:_ri,error:"beforeEvent: "+_e});counters.errors++;}')),t.push("matched.push(_ri);counters.rulesMatched++;"),s&&(t.push("let params;"),t.push('try{params=$.runMw(mw,stored.definition,ctx,_ep);}catch(_e){errors.push({ruleIndex:_ri,error:"Middleware: "+_e});counters.errors++;continue;}'));let l=s?"params":"_ep",h=r?`await ae.invokeAsync(stored.actionId,${l})`:`ae.invoke(stored.actionId,${l})`;t.push(`const ir=${h};`),t.push("counters.directivesApplied+=ir.appliedCount;counters.directivesSkipped+=ir.skippedCount;counters.errors+=ir.errors.length;"),a&&(t.push("try{"),t.push(`const _ad=${_}_hookAfter(stored.definition,ir,evalCtx);`),t.push('if(_ad==="abort"){break;}'),t.push("}catch(_e){}")),t.push("if(ir.aborted){halted=true;haltedBy=ir.abortedBy;break;}"),t.push("}"),t.push("aggCounters.rulesEvaluated+=counters.rulesEvaluated;"),t.push("aggCounters.rulesMatched+=counters.rulesMatched;"),t.push("aggCounters.rulesSkipped+=counters.rulesSkipped;"),t.push("aggCounters.directivesApplied+=counters.directivesApplied;"),t.push("aggCounters.directivesSkipped+=counters.directivesSkipped;"),t.push("aggCounters.subRunsCreated+=counters.subRunsCreated;"),t.push("aggCounters.errors+=counters.errors;"),t.push("for(let _k=0;_k<errors.length;_k++)aggErrors.push(errors[_k]);"),t.push("eventResults.push({event:ev.event,matched,skipped,notMatched,errors,halted,haltedBy,counters});"),t.push("}"),t.push("const _r={eventResults,unprocessedEvents,errors:aggErrors,counters:aggCounters,totalEvents:events.length,processedEvents:eventResults.length};"),u&&t.push(`try{${b}_hookOnComplete(_r);}catch(_e){}`),t.push("return _r;");let A=t.join(`
2
+ `),p=r?"async ":"",y=new Function("listenerIndex","events","ctx","ae","mw","hooks","$",`"use strict";
3
+ return(${p}()=>{
4
+ ${A}
5
+ })();`),x={};return s&&(x.runMw=M),function(S,C,c,k,T,D){return y(S,C,c,k,T,D,x)}}function V(e){return !e.event||typeof e.event!="string"?{event:e.event??"(missing)",code:"INVALID_DEFINITION",message:"event definition must have a non-empty string event name"}:e.tags!==void 0&&!Array.isArray(e.tags)?{event:e.event,code:"INVALID_DEFINITION",message:"event definition tags must be an array of strings"}:e.tier!==void 0&&typeof e.tier!="number"?{event:e.event,code:"INVALID_DEFINITION",message:"event definition tier must be a number"}:null}function j(e){if(!e.id||typeof e.id!="string")return {listenerId:e.id??"(missing)",code:"INVALID_LISTENER",message:"listener must have a string id"};if(typeof e.priority!="number")return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener must have a numeric priority"};if(typeof e.on=="string"){if(!e.on)return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener.on must be a non-empty string or string[]"}}else if(Array.isArray(e.on)){if(e.on.length===0||e.on.some(s=>typeof s!="string"||!s))return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener.on must be a non-empty string or string[] of non-empty strings"}}else return {listenerId:e.id,code:"INVALID_LISTENER",message:"listener.on must be a string or string[]"};return !Array.isArray(e.then)||e.then.length===0?{listenerId:e.id,code:"INVALID_LISTENER",message:"listener must have a non-empty then array"}:null}function H(e,s){let r=0,i=e.length;for(;r<i;){let o=r+i>>>1;e[o].priority>=s.priority?r=o+1:i=o;}e.splice(r,0,s);}function W(e,s){let r=e.indexOf(s);return r===-1?false:(e.splice(r,1),true)}var P=()=>{},$=class{_actionEngine;_middleware;_eventHooks;_isAsync;_requestedMode;_hookAnalysis;_dispatcher;_mode;_maybeAutoPromote;_eventDefinitions=new Map;_listenerRegistry=new Map;_listenerIndex=new Map;_batch=null;constructor(s){if(this._actionEngine=s.actionEngine,this._middleware=s.middleware??[],this._eventHooks=s.eventHooks??{},this._requestedMode=s.mode??"auto",this._hookAnalysis=analyzeSlots(this._eventHooks,EVENT_SLOT_NAMES),this._isAsync=this._hookAnalysis.hasAnyAsync||this._actionEngine.isAsync,this._requestedMode==="jit")this._dispatcher=this._buildJit(),this._mode="jit",this._maybeAutoPromote=P;else if(this._dispatcher=B(this._hookAnalysis,this._isAsync),this._mode="interpret",this._requestedMode==="auto"){let r=0,i=s.autoJitThreshold??8;this._maybeAutoPromote=()=>{++r>=i&&this._promote();};}else this._maybeAutoPromote=P;}get actionEngine(){return this._actionEngine}get isAsync(){return this._isAsync}get compilationMode(){return this._mode}get eventDefinitions(){return this._eventDefinitions}getEventDefinition(s){return this._eventDefinitions.get(s)}defineEvents(s){let r=[],i=[],o=[];for(let n of s){let a=V(n);if(a){i.push(a);continue}if(this._eventDefinitions.has(n.event)){i.push({event:n.event,code:"DUPLICATE_EVENT",message:`event definition "${n.event}" is already registered`});continue}this._eventDefinitions.set(n.event,n);let u=["eventdef"];n.tags&&u.push(...n.tags),o.push({id:`eventdef:${n.event}`,directives:[],tags:u,declarations:n.declarations,tier:n.tier,metadata:{...n.schema!==void 0&&{schema:n.schema},...n.description!==void 0&&{description:n.description}}}),r.push(n.event);}let d=[];if(o.length>0){let n=this._actionEngine.register(o);for(let a of n.errors){let u=this._actionIdToEventName(a.actionId);i.push({event:u??a.actionId,code:a.code??"ACTION_ERROR",message:a.error});}d=n.warnings;}let g={defined:r,errors:i,warnings:d};return this._batch&&(this._batch.definedEvents.push(...r),this._batch.defineErrors.push(...i),this._batch.warnings.push(...d)),g}register(s){let r=[],i=[],o=[],d=[];for(let n of s){let a=j(n);if(a){i.push(a);continue}if(this._listenerRegistry.has(n.id)){i.push({listenerId:n.id,code:"DUPLICATE_ID",message:`listener "${n.id}" is already registered`});continue}let u=typeof n.on=="string"?[n.on]:n.on,m=false;for(let l of u){let h=this._eventDefinitions.get(l);if(!h){o.push({actionId:`event:${n.id}`,code:"NO_EVENT_DEFINITION",message:`listener "${n.id}" listens to "${l}" but no event definition is registered for it`});continue}h.tier!==void 0&&n.declarations?.tier!==void 0&&n.declarations.tier<h.tier&&(i.push({listenerId:n.id,code:"TIER_VIOLATION",message:`listener "${n.id}" declares tier ${n.declarations.tier} but event "${l}" requires tier >= ${h.tier}`}),m=true);}if(m)continue;let _=`event:${n.id}`,b={definition:n,actionId:_,priority:n.priority};this._listenerRegistry.set(n.id,b);for(let l of u){let h=this._listenerIndex.get(l);h||(h=[],this._listenerIndex.set(l,h)),H(h,b);}let t=["event"];n.tags&&t.push(...n.tags),d.push({id:_,tags:t,directives:n.then,declarations:n.declarations}),r.push(n.id);}if(d.length>0){let n=this._actionEngine.register(d);for(let a of n.errors){let u=this._actionIdToListenerId(a.actionId);i.push({listenerId:u??a.actionId,code:a.code??"ACTION_ERROR",message:a.error});}o.push(...n.warnings);}let g={registered:r,errors:i,warnings:o};return this._batch&&(this._batch.registered.push(...r),this._batch.errors.push(...i),this._batch.warnings.push(...o)),g}unregister(s){let r=this._listenerRegistry.get(s);if(!r)return false;this._listenerRegistry.delete(s);let i=typeof r.definition.on=="string"?[r.definition.on]:r.definition.on;for(let o of i){let d=this._listenerIndex.get(o);d&&(W(d,r),d.length===0&&this._listenerIndex.delete(o));}return this._actionEngine.unregister(r.actionId),true}processEvents(s,r){if(this._isAsync)throw new Error("Cannot call processEvents() with async hooks. Use processEventsAsync() instead.");let i=this._dispatcher(this._listenerIndex,s,r,this._actionEngine,this._middleware,this._eventHooks);return this._maybeAutoPromote(),i}async processEventsAsync(s,r){let i=await this._dispatcher(this._listenerIndex,s,r,this._actionEngine,this._middleware,this._eventHooks);return this._maybeAutoPromote(),i}beginBatch(){this._batch||(this._batch={depth:0,registered:[],errors:[],warnings:[],definedEvents:[],defineErrors:[]}),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 s=this._actionEngine.endBatch();if(this._batch.depth>0)return {registered:[],errors:[],warnings:[]};let r=this._batch;for(let o of s.errors){let d=this._actionIdToListenerId(o.actionId);r.errors.push({listenerId:d??o.actionId,code:o.code??"ACTION_ERROR",message:o.error});}r.warnings.push(...s.warnings);let i={registered:r.registered,errors:r.errors,warnings:r.warnings};return this._batch=null,i}compile(){this._requestedMode!=="interpret"&&this._mode!=="jit"&&this._promote();}_promote(){this._dispatcher=this._buildJit(),this._mode="jit",this._maybeAutoPromote=P;}_buildJit(){return F(this._hookAnalysis,this._middleware.length>0,this._isAsync)}_actionIdToListenerId(s){return s.startsWith("event:")?s.slice(6):null}_actionIdToEventName(s){return s.startsWith("eventdef:")?s.slice(9):null}};function z(e){return new $(e)}function re(e="event"){return s=>{let r=s[e],i=typeof r=="string"&&r?[`eventdef:${r}`]:[];return {capabilities:["emit"],dependencies:i}}}export{re as createEmitAnalyzer,z as createEventProcessor};
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@statedelta-actions/events",
3
+ "version": "0.1.0",
4
+ "description": "Event processing engine with listener dispatch and JIT optimization",
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
+ "@statedelta-actions/analyzer": "0.1.0",
26
+ "@statedelta-actions/graph": "0.1.0"
27
+ },
28
+ "author": "Anderson D. Rosa <andersondrosa@outlook.com>",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/andersondrosa/statedelta-actions.git",
33
+ "directory": "packages/events"
34
+ },
35
+ "sideEffects": false,
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "dev": "tsup --watch",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "test:coverage": "vitest run --coverage",
48
+ "lint": "eslint src/",
49
+ "typecheck": "tsc --noEmit",
50
+ "clean": "rm -rf dist"
51
+ }
52
+ }