@saulwade/swl-ses 1.5.0 → 1.5.2
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/CLAUDE.md +19 -2
- package/README.md +561 -561
- package/agentes/arquitecto-swl.md +33 -1
- package/agentes/nemesis-auditor-swl.md +59 -19
- package/bin/swl-mcp-server.js +214 -214
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +230 -56
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/SKILL.md +225 -1
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/nemesis-evaluacion-json/SKILL.md +266 -0
- package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
- package/habilidades/node-experto/SKILL.md +105 -4
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/protocolo-revision-swl/SKILL.md +350 -276
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/tdd-workflow/SKILL.md +150 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/verificar-trabajo/SKILL.md +8 -3
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/check-update.js +31 -3
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +1324 -1321
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +2 -2
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +353 -351
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/registro-componentes-nuevos.md +192 -0
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/actualizar.js +110 -1
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/derivar-feature-list.js +489 -489
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +58 -4
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/mcp_config.py +127 -0
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/transformadores/codex.js +375 -375
- package/scripts/lib/transformadores/cursor.js +359 -359
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-orchestrator.py +8 -18
- package/scripts/mcp-pool-manager.py +12 -23
- package/scripts/mcp-server/README.md +170 -170
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/mcp-server/telemetry.js +78 -78
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* event-channel.js
|
|
5
|
-
*
|
|
6
|
-
* Pub/sub event channel para CommandRelay y otros componentes del gateway.
|
|
7
|
-
*
|
|
8
|
-
* Patrón adaptado de `temp/obsidian-agent-client-master/src/acp/acp-handler.ts`
|
|
9
|
-
* (Set<listeners> con callback unsubscribe). Diferencias:
|
|
10
|
-
* - Zero-deps Node.js (sin RxJS, sin EventEmitter de node — Set nativo).
|
|
11
|
-
* - Backward compat: si no hay listeners, comportamiento idéntico al actual.
|
|
12
|
-
* - Tipos de evento explícitos para evitar typos.
|
|
13
|
-
*
|
|
14
|
-
* Casos de uso en SWL:
|
|
15
|
-
* - CommandRelay emite eventos al recibir/aceptar/rechazar/procesar comandos.
|
|
16
|
-
* - Múltiples adaptadores (Telegram, Discord) pueden suscribirse simultáneamente.
|
|
17
|
-
* - El consumidor /swl:inbox puede mostrar progreso sin polling.
|
|
18
|
-
*
|
|
19
|
-
* Uso:
|
|
20
|
-
* const channel = new EventChannel();
|
|
21
|
-
* const off = channel.on('cmd:queued', (event) => console.log(event));
|
|
22
|
-
* channel.emit({ type: 'cmd:queued', commandId: 'cmd-abc', userId: '123' });
|
|
23
|
-
* off(); // unsubscribe
|
|
24
|
-
*
|
|
25
|
-
* @module gateway/lib/event-channel
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
// ── tipos de evento ──────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Tipos válidos de evento. Se usan strings para serializar fácilmente
|
|
32
|
-
* a JSONL si se requiere persistencia (no obligatorio).
|
|
33
|
-
*/
|
|
34
|
-
const EVENTS = Object.freeze({
|
|
35
|
-
// Lifecycle de comandos en CommandRelay
|
|
36
|
-
CMD_RECEIVED: 'cmd:received', // mensaje llegó al relay
|
|
37
|
-
CMD_REJECTED: 'cmd:rejected', // rechazado por validación (auth/rate/dedup)
|
|
38
|
-
CMD_QUEUED: 'cmd:queued', // encolado en .planning/inbox/
|
|
39
|
-
CMD_PROCESSED: 'cmd:processed', // marcado como procesado por consumidor
|
|
40
|
-
|
|
41
|
-
// Lifecycle de notificaciones
|
|
42
|
-
NOTIFICATION_SENT: 'notification:sent',
|
|
43
|
-
NOTIFICATION_FAILED: 'notification:failed',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// ── implementación ────────────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Canal de eventos pub/sub con escucha por tipo o wildcard.
|
|
50
|
-
*
|
|
51
|
-
* Mantiene dos estructuras:
|
|
52
|
-
* - listenersByType: Map<eventType, Set<callback>>
|
|
53
|
-
* - wildcardListeners: Set<callback> (escuchan TODOS los eventos)
|
|
54
|
-
*
|
|
55
|
-
* Una excepción en un listener NO interrumpe la propagación a los demás —
|
|
56
|
-
* patrón de aislamiento del mismo origen (acp-handler.ts:47-50).
|
|
57
|
-
*/
|
|
58
|
-
class EventChannel {
|
|
59
|
-
constructor() {
|
|
60
|
-
this.listenersByType = new Map();
|
|
61
|
-
this.wildcardListeners = new Set();
|
|
62
|
-
this.errorHandler = null; // opcional: callback para errores en listeners
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Suscribe un listener a un tipo de evento (o '*' para todos).
|
|
67
|
-
*
|
|
68
|
-
* @param {string} eventType - tipo de evento o '*'
|
|
69
|
-
* @param {function} callback - recibe el objeto event
|
|
70
|
-
* @returns {function} función de unsubscribe (idempotente)
|
|
71
|
-
*/
|
|
72
|
-
on(eventType, callback) {
|
|
73
|
-
if (typeof callback !== 'function') {
|
|
74
|
-
throw new TypeError('callback debe ser función');
|
|
75
|
-
}
|
|
76
|
-
if (typeof eventType !== 'string' || !eventType) {
|
|
77
|
-
throw new TypeError('eventType debe ser string no vacío');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (eventType === '*') {
|
|
81
|
-
this.wildcardListeners.add(callback);
|
|
82
|
-
return () => this.wildcardListeners.delete(callback);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!this.listenersByType.has(eventType)) {
|
|
86
|
-
this.listenersByType.set(eventType, new Set());
|
|
87
|
-
}
|
|
88
|
-
const set = this.listenersByType.get(eventType);
|
|
89
|
-
set.add(callback);
|
|
90
|
-
return () => set.delete(callback);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Suscribe un listener que se invoca UNA sola vez y luego se desuscribe.
|
|
95
|
-
*/
|
|
96
|
-
once(eventType, callback) {
|
|
97
|
-
const off = this.on(eventType, (event) => {
|
|
98
|
-
off();
|
|
99
|
-
callback(event);
|
|
100
|
-
});
|
|
101
|
-
return off;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Emite un evento a todos los listeners suscriptos al tipo + wildcards.
|
|
106
|
-
* El evento debe tener al menos `type`. Se enriquece con `ts` (ISO).
|
|
107
|
-
*
|
|
108
|
-
* @param {object} event - objeto con `type` y datos arbitrarios
|
|
109
|
-
* @returns {number} cantidad de listeners notificados
|
|
110
|
-
*/
|
|
111
|
-
emit(event) {
|
|
112
|
-
if (!event || typeof event !== 'object') {
|
|
113
|
-
throw new TypeError('event debe ser objeto');
|
|
114
|
-
}
|
|
115
|
-
if (typeof event.type !== 'string' || !event.type) {
|
|
116
|
-
throw new TypeError('event.type es obligatorio');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const enriched = { ts: new Date().toISOString(), ...event };
|
|
120
|
-
let notified = 0;
|
|
121
|
-
|
|
122
|
-
// Listeners específicos del tipo
|
|
123
|
-
const typedSet = this.listenersByType.get(enriched.type);
|
|
124
|
-
if (typedSet) {
|
|
125
|
-
for (const cb of typedSet) {
|
|
126
|
-
notified++;
|
|
127
|
-
this._safeInvoke(cb, enriched);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Listeners wildcard
|
|
132
|
-
for (const cb of this.wildcardListeners) {
|
|
133
|
-
notified++;
|
|
134
|
-
this._safeInvoke(cb, enriched);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return notified;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Cuenta de listeners para un tipo (o total si no se pasa tipo).
|
|
142
|
-
*/
|
|
143
|
-
listenerCount(eventType) {
|
|
144
|
-
if (eventType == null) {
|
|
145
|
-
let total = this.wildcardListeners.size;
|
|
146
|
-
for (const set of this.listenersByType.values()) total += set.size;
|
|
147
|
-
return total;
|
|
148
|
-
}
|
|
149
|
-
if (eventType === '*') return this.wildcardListeners.size;
|
|
150
|
-
const set = this.listenersByType.get(eventType);
|
|
151
|
-
return (set ? set.size : 0) + this.wildcardListeners.size;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Limpia todos los listeners. Útil para tests.
|
|
156
|
-
*/
|
|
157
|
-
clear() {
|
|
158
|
-
this.listenersByType.clear();
|
|
159
|
-
this.wildcardListeners.clear();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Asigna un handler para errores en listeners.
|
|
164
|
-
* Si no se asigna, los errores se silencian (no rompen la propagación).
|
|
165
|
-
*/
|
|
166
|
-
onError(handler) {
|
|
167
|
-
if (typeof handler !== 'function') {
|
|
168
|
-
throw new TypeError('handler debe ser función');
|
|
169
|
-
}
|
|
170
|
-
this.errorHandler = handler;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ── internos ────────────────────────────────────────────────────────────────
|
|
174
|
-
|
|
175
|
-
_safeInvoke(cb, event) {
|
|
176
|
-
try {
|
|
177
|
-
cb(event);
|
|
178
|
-
} catch (err) {
|
|
179
|
-
if (this.errorHandler) {
|
|
180
|
-
try { this.errorHandler(err, event); } catch (_) { /* silencioso */ }
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ── exports ───────────────────────────────────────────────────────────────────
|
|
187
|
-
|
|
188
|
-
module.exports = {
|
|
189
|
-
EventChannel,
|
|
190
|
-
EVENTS,
|
|
191
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* event-channel.js
|
|
5
|
+
*
|
|
6
|
+
* Pub/sub event channel para CommandRelay y otros componentes del gateway.
|
|
7
|
+
*
|
|
8
|
+
* Patrón adaptado de `temp/obsidian-agent-client-master/src/acp/acp-handler.ts`
|
|
9
|
+
* (Set<listeners> con callback unsubscribe). Diferencias:
|
|
10
|
+
* - Zero-deps Node.js (sin RxJS, sin EventEmitter de node — Set nativo).
|
|
11
|
+
* - Backward compat: si no hay listeners, comportamiento idéntico al actual.
|
|
12
|
+
* - Tipos de evento explícitos para evitar typos.
|
|
13
|
+
*
|
|
14
|
+
* Casos de uso en SWL:
|
|
15
|
+
* - CommandRelay emite eventos al recibir/aceptar/rechazar/procesar comandos.
|
|
16
|
+
* - Múltiples adaptadores (Telegram, Discord) pueden suscribirse simultáneamente.
|
|
17
|
+
* - El consumidor /swl:inbox puede mostrar progreso sin polling.
|
|
18
|
+
*
|
|
19
|
+
* Uso:
|
|
20
|
+
* const channel = new EventChannel();
|
|
21
|
+
* const off = channel.on('cmd:queued', (event) => console.log(event));
|
|
22
|
+
* channel.emit({ type: 'cmd:queued', commandId: 'cmd-abc', userId: '123' });
|
|
23
|
+
* off(); // unsubscribe
|
|
24
|
+
*
|
|
25
|
+
* @module gateway/lib/event-channel
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// ── tipos de evento ──────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Tipos válidos de evento. Se usan strings para serializar fácilmente
|
|
32
|
+
* a JSONL si se requiere persistencia (no obligatorio).
|
|
33
|
+
*/
|
|
34
|
+
const EVENTS = Object.freeze({
|
|
35
|
+
// Lifecycle de comandos en CommandRelay
|
|
36
|
+
CMD_RECEIVED: 'cmd:received', // mensaje llegó al relay
|
|
37
|
+
CMD_REJECTED: 'cmd:rejected', // rechazado por validación (auth/rate/dedup)
|
|
38
|
+
CMD_QUEUED: 'cmd:queued', // encolado en .planning/inbox/
|
|
39
|
+
CMD_PROCESSED: 'cmd:processed', // marcado como procesado por consumidor
|
|
40
|
+
|
|
41
|
+
// Lifecycle de notificaciones
|
|
42
|
+
NOTIFICATION_SENT: 'notification:sent',
|
|
43
|
+
NOTIFICATION_FAILED: 'notification:failed',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ── implementación ────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Canal de eventos pub/sub con escucha por tipo o wildcard.
|
|
50
|
+
*
|
|
51
|
+
* Mantiene dos estructuras:
|
|
52
|
+
* - listenersByType: Map<eventType, Set<callback>>
|
|
53
|
+
* - wildcardListeners: Set<callback> (escuchan TODOS los eventos)
|
|
54
|
+
*
|
|
55
|
+
* Una excepción en un listener NO interrumpe la propagación a los demás —
|
|
56
|
+
* patrón de aislamiento del mismo origen (acp-handler.ts:47-50).
|
|
57
|
+
*/
|
|
58
|
+
class EventChannel {
|
|
59
|
+
constructor() {
|
|
60
|
+
this.listenersByType = new Map();
|
|
61
|
+
this.wildcardListeners = new Set();
|
|
62
|
+
this.errorHandler = null; // opcional: callback para errores en listeners
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Suscribe un listener a un tipo de evento (o '*' para todos).
|
|
67
|
+
*
|
|
68
|
+
* @param {string} eventType - tipo de evento o '*'
|
|
69
|
+
* @param {function} callback - recibe el objeto event
|
|
70
|
+
* @returns {function} función de unsubscribe (idempotente)
|
|
71
|
+
*/
|
|
72
|
+
on(eventType, callback) {
|
|
73
|
+
if (typeof callback !== 'function') {
|
|
74
|
+
throw new TypeError('callback debe ser función');
|
|
75
|
+
}
|
|
76
|
+
if (typeof eventType !== 'string' || !eventType) {
|
|
77
|
+
throw new TypeError('eventType debe ser string no vacío');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (eventType === '*') {
|
|
81
|
+
this.wildcardListeners.add(callback);
|
|
82
|
+
return () => this.wildcardListeners.delete(callback);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!this.listenersByType.has(eventType)) {
|
|
86
|
+
this.listenersByType.set(eventType, new Set());
|
|
87
|
+
}
|
|
88
|
+
const set = this.listenersByType.get(eventType);
|
|
89
|
+
set.add(callback);
|
|
90
|
+
return () => set.delete(callback);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Suscribe un listener que se invoca UNA sola vez y luego se desuscribe.
|
|
95
|
+
*/
|
|
96
|
+
once(eventType, callback) {
|
|
97
|
+
const off = this.on(eventType, (event) => {
|
|
98
|
+
off();
|
|
99
|
+
callback(event);
|
|
100
|
+
});
|
|
101
|
+
return off;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Emite un evento a todos los listeners suscriptos al tipo + wildcards.
|
|
106
|
+
* El evento debe tener al menos `type`. Se enriquece con `ts` (ISO).
|
|
107
|
+
*
|
|
108
|
+
* @param {object} event - objeto con `type` y datos arbitrarios
|
|
109
|
+
* @returns {number} cantidad de listeners notificados
|
|
110
|
+
*/
|
|
111
|
+
emit(event) {
|
|
112
|
+
if (!event || typeof event !== 'object') {
|
|
113
|
+
throw new TypeError('event debe ser objeto');
|
|
114
|
+
}
|
|
115
|
+
if (typeof event.type !== 'string' || !event.type) {
|
|
116
|
+
throw new TypeError('event.type es obligatorio');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const enriched = { ts: new Date().toISOString(), ...event };
|
|
120
|
+
let notified = 0;
|
|
121
|
+
|
|
122
|
+
// Listeners específicos del tipo
|
|
123
|
+
const typedSet = this.listenersByType.get(enriched.type);
|
|
124
|
+
if (typedSet) {
|
|
125
|
+
for (const cb of typedSet) {
|
|
126
|
+
notified++;
|
|
127
|
+
this._safeInvoke(cb, enriched);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Listeners wildcard
|
|
132
|
+
for (const cb of this.wildcardListeners) {
|
|
133
|
+
notified++;
|
|
134
|
+
this._safeInvoke(cb, enriched);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return notified;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cuenta de listeners para un tipo (o total si no se pasa tipo).
|
|
142
|
+
*/
|
|
143
|
+
listenerCount(eventType) {
|
|
144
|
+
if (eventType == null) {
|
|
145
|
+
let total = this.wildcardListeners.size;
|
|
146
|
+
for (const set of this.listenersByType.values()) total += set.size;
|
|
147
|
+
return total;
|
|
148
|
+
}
|
|
149
|
+
if (eventType === '*') return this.wildcardListeners.size;
|
|
150
|
+
const set = this.listenersByType.get(eventType);
|
|
151
|
+
return (set ? set.size : 0) + this.wildcardListeners.size;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Limpia todos los listeners. Útil para tests.
|
|
156
|
+
*/
|
|
157
|
+
clear() {
|
|
158
|
+
this.listenersByType.clear();
|
|
159
|
+
this.wildcardListeners.clear();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Asigna un handler para errores en listeners.
|
|
164
|
+
* Si no se asigna, los errores se silencian (no rompen la propagación).
|
|
165
|
+
*/
|
|
166
|
+
onError(handler) {
|
|
167
|
+
if (typeof handler !== 'function') {
|
|
168
|
+
throw new TypeError('handler debe ser función');
|
|
169
|
+
}
|
|
170
|
+
this.errorHandler = handler;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── internos ────────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
_safeInvoke(cb, event) {
|
|
176
|
+
try {
|
|
177
|
+
cb(event);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (this.errorHandler) {
|
|
180
|
+
try { this.errorHandler(err, event); } catch (_) { /* silencioso */ }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── exports ───────────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
EventChannel,
|
|
190
|
+
EVENTS,
|
|
191
|
+
};
|