You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
692 lines
18 KiB
692 lines
18 KiB
Clippy-pet verdict: `orca` es una ballena preciosa, pero si intentamos montarla entera en v0 nos hunde la barca.
|
|
|
|
Lo correcto:
|
|
|
|
```txt
|
|
v0 = kernel
|
|
v1 = enterprise orchestration
|
|
```
|
|
|
|
`orca` está bien pensado porque tiene tres aciertos enormes:
|
|
|
|
- Desacopla módulos: `orca` no sabe qué es `cach`, `perm`, `sess` o `connection`.
|
|
- Convierte flujos críticos en artefactos trazables: `OrcaRunResult`.
|
|
- Usa tokens como hechos semánticos per-run, que es la parte diferencial.
|
|
|
|
Pero ahora mismo el README mezcla kernel y futuro. Para que sea implementable, v0 debería recortarse así:
|
|
|
|
```txt
|
|
entra en v0:
|
|
setupOrca
|
|
createEngineOrca
|
|
App.Orchestration inerte
|
|
onEvent
|
|
stages
|
|
queue/drop
|
|
tokens per-run flag
|
|
after/provides
|
|
OrcaResult success/skipped/error/fatal/interrupted
|
|
OrcaRunResult
|
|
validate()
|
|
diagnostics
|
|
|
|
sale de v0:
|
|
commit()
|
|
replace
|
|
timeouts
|
|
transaction
|
|
compensate
|
|
parallel
|
|
abortOn/unless avanzados si complican
|
|
tokens con payload
|
|
fan-in
|
|
```
|
|
|
|
La pregunta decisiva no es “¿está bien diseñado?”. Sí, lo está.
|
|
|
|
La pregunta correcta es: **¿cuál es el núcleo mínimo que demuestra que `orca` merece existir?**
|
|
|
|
Respuesta: `event -> staged actions -> tokens -> result trace`.
|
|
|
|
Si eso funciona, lo demás se puede crecer con calma.
|
|
|
|
|
|
como evitamos la reentrada en orca , si no establecemos en el payload del evento los eventos que le preceden en el stack ? al disparar el componente verifica si esta gestionando ya un evento de ese stack y lo omitiria , como lo suelen hacer el evitar loops infinitos ?
|
|
|
|
|
|
La forma sana no es meter “eventos precedentes” en el payload de negocio. Eso contaminaría todos los eventos con metadata de orquestación y acabaría mezclando dominio con runtime.
|
|
|
|
Lo correcto es que `orca` envuelva cada evento en un **OrcaEnvelope** interno:
|
|
|
|
```ts
|
|
interface OrcaEnvelope<TPayload = unknown> {
|
|
readonly event: string;
|
|
readonly payload: TPayload;
|
|
readonly meta: {
|
|
readonly eventId: string;
|
|
readonly parentEventId?: string;
|
|
readonly runId?: string;
|
|
readonly traceId: string;
|
|
readonly depth: number;
|
|
readonly stack: readonly string[];
|
|
readonly emittedBy?: OrcaActionId;
|
|
};
|
|
}
|
|
```
|
|
|
|
El payload sigue limpio:
|
|
|
|
```ts
|
|
{ previousActorId, nextActorId }
|
|
```
|
|
|
|
Y `orca` conserva el contexto:
|
|
|
|
```ts
|
|
{
|
|
event: APP_EVENT_CACHE_CLEARED,
|
|
meta: {
|
|
parentEventId: 'evt-1',
|
|
traceId: 'trace-7',
|
|
depth: 2,
|
|
stack: [
|
|
APP_EVENT_USER_IDENTITY_CHANGED,
|
|
APP_EVENT_CACHE_CLEARED
|
|
],
|
|
emittedBy: ORCA_ACTION_CLEAR_PRIVATE_CACHE
|
|
}
|
|
}
|
|
```
|
|
|
|
Entonces la prevención de loops vive en `orca`, no en los módulos.
|
|
|
|
Reglas típicas para evitar loops infinitos:
|
|
|
|
```txt
|
|
1. No ejecutar inline eventos emitidos durante un run.
|
|
2. Encolarlos con traceId y parentEventId.
|
|
3. Mantener stack interno por trace.
|
|
4. Rechazar o saltar eventos si superan maxDepth.
|
|
5. Rechazar o saltar si el mismo evento aparece demasiado en el mismo trace.
|
|
6. Aplicar dedupeKey opcional.
|
|
7. Tener maxEventsPerTrace.
|
|
8. Tener diagnostics cuando se corta el loop.
|
|
```
|
|
|
|
Ejemplo:
|
|
|
|
```ts
|
|
const Orca = createEngineOrca({
|
|
bus,
|
|
timers,
|
|
logger,
|
|
reentry: {
|
|
maxDepth: 16,
|
|
maxEventsPerTrace: 128,
|
|
repeatedEventPolicy: ORCA_REENTRY_SKIP,
|
|
repeatedEventLimit: 2
|
|
}
|
|
});
|
|
```
|
|
|
|
Si ocurre esto:
|
|
|
|
```txt
|
|
A -> action publica B
|
|
B -> action publica A
|
|
A -> action publica B
|
|
...
|
|
```
|
|
|
|
`orca` ve el trace:
|
|
|
|
```txt
|
|
A, B, A, B, A
|
|
```
|
|
|
|
Y puede cortar:
|
|
|
|
```ts
|
|
ORCA_RESULT_INTERRUPTED
|
|
ORCA_REENTRY_REASON_REPEATED_EVENT
|
|
```
|
|
|
|
o marcar el run como:
|
|
|
|
```ts
|
|
ORCA_RUN_ABORTED
|
|
```
|
|
|
|
Los patrones habituales en otros sistemas:
|
|
|
|
- Redux middleware suele evitar loops por convención y con guards manuales.
|
|
- Redux-Saga usa `takeLatest`, cancelación y control de tasks.
|
|
- EventEmitter no te protege: si emites recursivo, te comes el loop.
|
|
- State machines evitan loops por transición/estado: si ya estás en cierto estado, el evento no transiciona.
|
|
- Sistemas workflow usan `trace/correlationId`, depth limits, idempotency keys y dedupe.
|
|
|
|
Para `orca`, yo fijaría esto:
|
|
|
|
```txt
|
|
payload = dominio, limpio
|
|
envelope/meta = runtime, trace, stack, depth
|
|
tokens = coordinación intra-run
|
|
bus events = comunicación inter-run
|
|
```
|
|
|
|
Y añadiría al contrato v0:
|
|
|
|
```ts
|
|
interface OrcaReentryOptions {
|
|
readonly maxDepth?: number; // default 16
|
|
readonly maxEventsPerTrace?: number; // default 128
|
|
readonly repeatedEventLimit?: number; // default 1 o 2
|
|
readonly repeatedEventPolicy?:
|
|
| typeof ORCA_REENTRY_SKIP
|
|
| typeof ORCA_REENTRY_ABORT_TRACE
|
|
| typeof ORCA_REENTRY_ERROR;
|
|
}
|
|
```
|
|
|
|
Así evitas reentrada sin ensuciar los payloads y sin obligar a cada módulo/componente a recordar “quién vino antes”.
|
|
|
|
|
|
cual estrategia es mas arquitectonicamente mejor ?
|
|
|
|
|
|
Arquitectónicamente, la mejor estrategia es:
|
|
|
|
```txt
|
|
payload limpio + envelope runtime interno + cola de orca + límites de reentrada
|
|
```
|
|
|
|
No metería el stack en el payload del evento.
|
|
|
|
Por qué:
|
|
|
|
- El payload pertenece al dominio: `userId`, `tenantId`, `orderId`, etc.
|
|
- El stack pertenece al runtime de orquestación.
|
|
- Si mezclas ambos, todos los módulos empiezan a conocer detalles de `orca`.
|
|
- Si mañana cambias cómo trazas reentrada, rompes contratos de eventos.
|
|
- Los eventos pueden venir de `connection`, `auth`, `sess`, `http`, etc.; no todos deberían cargar metadata de runtime.
|
|
|
|
La arquitectura correcta sería:
|
|
|
|
```txt
|
|
buss event:
|
|
type + payload limpio
|
|
|
|
orca internal envelope:
|
|
eventId
|
|
traceId
|
|
parentEventId
|
|
runId
|
|
depth
|
|
stack
|
|
emittedByAction
|
|
dedupeKey
|
|
```
|
|
|
|
Flujo:
|
|
|
|
```txt
|
|
1. buss publica EVENT_A(payload)
|
|
2. orca crea envelope A con traceId
|
|
3. action de A publica EVENT_B
|
|
4. orca no ejecuta B inline
|
|
5. orca crea envelope B con parent=A, mismo traceId, depth+1
|
|
6. orca comprueba stack/repeated/depth
|
|
7. si es seguro, encola B
|
|
8. si detecta loop, corta con diagnostic
|
|
```
|
|
|
|
Esto es más limpio que “cada componente mira si ya está gestionando un evento de ese stack”, porque esa opción reparte lógica de orquestación por toda la app. Es justo lo que `orca` intenta evitar.
|
|
|
|
Estrategia recomendada para v0:
|
|
|
|
```ts
|
|
createEngineOrca({
|
|
bus,
|
|
logger,
|
|
reentry: {
|
|
maxDepth: 16,
|
|
maxEventsPerTrace: 128,
|
|
repeatedEventLimit: 2,
|
|
repeatedEventPolicy: ORCA_REENTRY_SKIP
|
|
}
|
|
});
|
|
```
|
|
|
|
Reglas:
|
|
|
|
- Eventos publicados durante un run se encolan, no se ejecutan inline.
|
|
- El stack es interno de `orca`.
|
|
- Si el mismo evento aparece demasiadas veces en el mismo trace, se salta o aborta.
|
|
- Si depth supera límite, aborta el trace.
|
|
- Si eventsPerTrace supera límite, aborta el trace.
|
|
- Se emite diagnostic estructurado.
|
|
- El `OrcaRunResult` registra `traceId`, `parentEventId`, `depth`, `interruptedReason`.
|
|
|
|
Mi voto: **envelope interno + queue + trace guards**. Es la opción más limpia, menos acoplada y más mantenible.
|
|
|
|
|
|
|
|
me refiero a los otros sistemas de referencia
|
|
|
|
|
|
|
|
En los sistemas de referencia, la estrategia arquitectónicamente mejor **no es meter el stack en el payload**. Casi todos los sistemas buenos separan:
|
|
|
|
```txt
|
|
payload = datos de negocio
|
|
runtime context = control, cancelación, stack, tracing, concurrencia
|
|
```
|
|
|
|
**Comparativa**
|
|
| Sistema | Cómo evita loops/reentrada | Lectura para `orca` |
|
|
|---|---|---|
|
|
| RTK Listener Middleware | Usa runtime API: `signal`, `cancelActiveListeners()`, `cancel()`, `take`, `condition`, `delay`, `pause`, `fork`. No mete stack en la action. | Muy buen referente. `orca` debería pasar `signal`, `traceId`, `runId` y control de cancelación en `OrcaActionContext`. Fuente: [RTK listener middleware](https://redux-toolkit.js.org/api/createListenerMiddleware). |
|
|
| Redux-Saga | Usa tareas y cancelación: `takeLatest` cancela la tarea anterior; `takeEvery` permite concurrencia; `race`, `cancel`, `fork`, `join`. | Bueno para políticas `queue/drop/replace/parallel`, pero no copiaría generators. Fuente: [Redux-Saga concurrency](https://redux-saga.js.org/docs/advanced/Concurrency/). |
|
|
| XState | Evita loops mediante estado, guards y transiciones. Si ya estás en un estado, un evento puede no transicionar. El control vive en la máquina, no en el payload. | Útil para flows cerrados; para `orca`, copiaría `setup()` tipado y la idea de runtime/state como dueño del control. Fuente: [XState setup](https://stately.ai/docs/setup). |
|
|
| Effect Workflow | Distingue error/cause/interruption y tiene compensación estructurada. El runtime sabe qué falló y cómo compensar; no lo mete en payload. | Muy buen referente para `OrcaInterrupted`, compensaciones futuras y `Cause`. Fuente: [Effect Workflow compensation](https://effect-ts.github.io/effect/workflow/Workflow.ts.html). |
|
|
| EventEmitter simple | No evita loops. Si un listener emite el mismo evento recursivamente, problema del usuario. | Esto es justo lo que `orca` no debe ser. |
|
|
|
|
**Veredicto**
|
|
La mejor estrategia para `orca` es una mezcla de RTK Listener + Saga + XState:
|
|
|
|
```txt
|
|
payload limpio
|
|
OrcaEnvelope interno
|
|
cola propia de orca
|
|
traceId / parentEventId / runId
|
|
AbortSignal en OrcaActionContext
|
|
política de concurrencia por evento
|
|
límites maxDepth / maxEventsPerTrace
|
|
diagnostics cuando corta loops
|
|
```
|
|
|
|
No pondría el stack en el payload. Eso es arquitectura más débil porque obliga a todos los módulos a conocer el runtime de `orca`.
|
|
|
|
La estrategia superior es:
|
|
|
|
```ts
|
|
interface OrcaActionContext {
|
|
readonly runId: OrcaRunId;
|
|
readonly eventId: OrcaEventId;
|
|
readonly traceId: OrcaTraceId;
|
|
readonly parentEventId?: OrcaEventId;
|
|
readonly depth: number;
|
|
readonly signal: AbortSignal;
|
|
}
|
|
```
|
|
|
|
Y el envelope interno:
|
|
|
|
```ts
|
|
interface OrcaEnvelope<TPayload> {
|
|
readonly event: string;
|
|
readonly payload: TPayload;
|
|
readonly meta: OrcaEventMeta;
|
|
}
|
|
```
|
|
|
|
Así `orca` se parece a los sistemas buenos: el **runtime** controla reentrada, cancelación y concurrencia; el **payload** sigue siendo dominio limpio.
|
|
|
|
|
|
ahora mismo no se parece a eso en nada ?
|
|
|
|
|
|
|
|
|
|
Correcto: **el diseño actual apunta en esa dirección, pero todavía no se parece lo suficiente en contrato concreto**.
|
|
|
|
Ahora mismo el README ya dice cosas buenas:
|
|
|
|
```txt
|
|
payload limpio
|
|
no ejecutar inline
|
|
queue/drop/replace
|
|
tokens per-run
|
|
RunResult
|
|
App.Orchestration inerte
|
|
```
|
|
|
|
Pero faltan las piezas que lo harían parecerse de verdad a RTK/Saga/XState/Effect en lo importante:
|
|
|
|
```txt
|
|
OrcaEnvelope interno
|
|
OrcaActionContext con traceId/runId/eventId/depth
|
|
AbortSignal en acciones
|
|
cola interna explícita de orca
|
|
políticas de reentrada: maxDepth, maxEventsPerTrace
|
|
reason/result para reentry-skip / interrupted
|
|
relación formal buss -> orca listener -> queue
|
|
```
|
|
|
|
Así que mi diagnóstico es:
|
|
|
|
```txt
|
|
Conceptualmente: sí está en el camino correcto.
|
|
Contractualmente: todavía no está cerrado.
|
|
Implementablemente: aún tendría ambigüedad peligrosa.
|
|
```
|
|
|
|
La sección que habría que añadir al README antes de implementar es algo como:
|
|
|
|
```ts
|
|
interface OrcaEnvelope<TPayload = unknown> {
|
|
readonly event: string;
|
|
readonly payload: TPayload;
|
|
readonly meta: OrcaEventMeta;
|
|
}
|
|
|
|
interface OrcaEventMeta {
|
|
readonly eventId: OrcaEventId;
|
|
readonly traceId: OrcaTraceId;
|
|
readonly parentEventId?: OrcaEventId;
|
|
readonly parentRunId?: OrcaRunId;
|
|
readonly emittedByAction?: OrcaActionId;
|
|
readonly depth: number;
|
|
readonly stack: readonly string[];
|
|
}
|
|
|
|
interface OrcaActionContext {
|
|
readonly runId: OrcaRunId;
|
|
readonly event: string;
|
|
readonly eventId: OrcaEventId;
|
|
readonly traceId: OrcaTraceId;
|
|
readonly depth: number;
|
|
readonly signal: AbortSignal;
|
|
readonly emit: <TPayload>(event: string, payload: TPayload) => void;
|
|
}
|
|
```
|
|
|
|
Y reglas:
|
|
|
|
```txt
|
|
1. Las acciones no llaman bus.publish directamente si quieren preservar trace.
|
|
2. Usan ctx.emit() para eventos derivados.
|
|
3. ctx.emit() crea envelope hijo.
|
|
4. Eventos derivados se encolan, no inline.
|
|
5. Orca corta loops con maxDepth/maxEventsPerTrace/repeatedEventLimit.
|
|
6. Si una acción llama App.Bus directamente, orca lo trata como evento raíz nuevo.
|
|
```
|
|
|
|
Esto último es importante: **si queremos trazabilidad real, las acciones deben emitir eventos derivados vía `ctx.emit()`**, no vía `App.Bus.publish()` directamente. Ahí está la diferencia entre “tenemos bus” y “tenemos runtime de orquestación”.
|
|
|
|
Así que sí: ahora mismo aún no está del todo. Tiene el esqueleto filosófico, pero le falta el contrato de runtime/envelope/context para ser una pieza robusta.
|
|
|
|
|
|
|
|
|
|
|
|
ctx deberiamos definirlo, seria otra capa mas ?
|
|
|
|
|
|
|
|
|
|
Sí, `ctx` hay que definirlo. Pero no lo vería como “otra capa” al nivel de `buss`/`orca`/`timr`; lo vería como **el runtime context de una ejecución de `orca`**.
|
|
|
|
No es un artefacto nuevo. Es parte del contrato de `orca`.
|
|
|
|
```txt
|
|
orca = engine
|
|
run = ejecución de un evento
|
|
ctx = contexto que orca entrega a cada action dentro de ese run
|
|
```
|
|
|
|
El `ctx` es donde ponemos todo lo que no debe ir en el payload:
|
|
|
|
```ts
|
|
interface OrcaActionContext {
|
|
readonly runId: OrcaRunId;
|
|
readonly eventId: OrcaEventId;
|
|
readonly traceId: OrcaTraceId;
|
|
readonly parentEventId?: OrcaEventId;
|
|
readonly depth: number;
|
|
|
|
readonly signal: AbortSignal;
|
|
|
|
emit<TPayload>(
|
|
event: string,
|
|
payload: TPayload,
|
|
options?: OrcaEmitOptions
|
|
): void;
|
|
|
|
token(token: OrcaToken): void;
|
|
|
|
hasToken(token: OrcaToken): boolean;
|
|
}
|
|
```
|
|
|
|
La separación queda:
|
|
|
|
```txt
|
|
payload
|
|
Datos del evento.
|
|
Pertenece al dominio.
|
|
|
|
ctx
|
|
Datos/control de ejecución.
|
|
Pertenece a orca.
|
|
|
|
App modules
|
|
Servicios reales.
|
|
La action los cierra por closure.
|
|
```
|
|
|
|
Ejemplo:
|
|
|
|
```ts
|
|
App.Orchestration.onEvent(APP_EVENT_USER_IDENTITY_CHANGED, {
|
|
id: ORCA_ACTION_CLEAR_PRIVATE_CACHE,
|
|
stage: ORCA_STAGE_MAIN,
|
|
provides: [ORCA_TOKEN_CACHE_OK, ORCA_TOKEN_CACHE_ERROR],
|
|
|
|
action: async (payload, ctx) => {
|
|
if (ctx.signal.aborted) {
|
|
return orcaInterrupted('run aborted');
|
|
}
|
|
|
|
const result = await App.Cache.clearActorScope(payload.previousActorId);
|
|
|
|
if (!result.ok) {
|
|
return orcaError(result.error, {
|
|
emits: [ORCA_TOKEN_CACHE_ERROR]
|
|
});
|
|
}
|
|
|
|
ctx.emit(APP_EVENT_PRIVATE_CACHE_CLEARED, {
|
|
actorId: payload.previousActorId
|
|
});
|
|
|
|
return orcaSuccess({
|
|
emits: [ORCA_TOKEN_CACHE_OK]
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
`ctx.emit()` es importante porque preserva traza:
|
|
|
|
```txt
|
|
run A
|
|
action clear-cache
|
|
ctx.emit(B)
|
|
|
|
orca crea envelope B:
|
|
traceId = A.traceId
|
|
parentEventId = A.eventId
|
|
emittedByAction = clear-cache
|
|
depth = A.depth + 1
|
|
```
|
|
|
|
Si la action llama directamente:
|
|
|
|
```ts
|
|
App.Bus.publish(B, payload)
|
|
```
|
|
|
|
entonces `orca` no sabe que B viene de A salvo que intercepte todo, lo cual es más frágil. Por eso yo pondría esta regla:
|
|
|
|
```txt
|
|
Dentro de acciones orca, usa ctx.emit() para eventos derivados.
|
|
Usa App.Bus.publish() solo para eventos raíz externos al run.
|
|
```
|
|
|
|
Entonces no es una capa adicional. Es el equivalente a:
|
|
|
|
- `listenerApi` en RTK listener middleware.
|
|
- `saga effects` en redux-saga.
|
|
- `context/event` en XState.
|
|
- `workflow context/cause` en Effect/Temporal.
|
|
|
|
Mi recomendación: sí, definir `OrcaActionContext` como contrato central de v0. Sin `ctx`, `orca` será solo un callback runner. Con `ctx`, empieza a ser runtime de orquestación.
|
|
|
|
|
|
|
|
y como sabe una accion de un modulo a la hora de emitir un evento donde lo debe hacer, por ejemplo
|
|
|
|
auth.onChangeUser (newUser) {
|
|
bus.publish(AUTH_CHANGE_EVENT,newUser)
|
|
o
|
|
ctx.publish(AUTH_CHANGE_EVENT,newUser)
|
|
}
|
|
|
|
|
|
|
|
La regla tiene que ser muy clara:
|
|
|
|
```txt
|
|
Un módulo nunca usa ctx.
|
|
Una acción de orca sí puede usar ctx.
|
|
```
|
|
|
|
Tu ejemplo:
|
|
|
|
```ts
|
|
auth.onChangeUser((newUser) => {
|
|
bus.publish(AUTH_CHANGE_EVENT, newUser);
|
|
});
|
|
```
|
|
|
|
Eso está bien si ocurre **dentro del módulo auth**. `auth` no debe saber que existe `orca`, por tanto no puede recibir ni usar `ctx`.
|
|
|
|
En cambio esto:
|
|
|
|
```ts
|
|
auth.onChangeUser((newUser) => {
|
|
ctx.publish(AUTH_CHANGE_EVENT, newUser);
|
|
});
|
|
```
|
|
|
|
Eso estaría mal si `auth.onChangeUser` es código del módulo. Meterías `orca` dentro de `auth`.
|
|
|
|
La separación correcta:
|
|
|
|
```txt
|
|
Módulo -> Bus
|
|
Orca action -> Ctx
|
|
```
|
|
|
|
Ejemplo completo:
|
|
|
|
```ts
|
|
// Dentro de auth
|
|
function setUser(nextUser: User) {
|
|
currentUser = nextUser;
|
|
|
|
bus.publish(AUTH_EVENT_USER_CHANGED, {
|
|
userId: nextUser.id
|
|
});
|
|
}
|
|
```
|
|
|
|
`auth` publica en `bus`. Punto. No conoce `orca`.
|
|
|
|
Ahora una acción de `orca`:
|
|
|
|
```ts
|
|
App.Orchestration.onEvent(AUTH_EVENT_USER_CHANGED, {
|
|
id: ORCA_ACTION_CLEAR_PRIVATE_STATE,
|
|
stage: ORCA_STAGE_MAIN,
|
|
|
|
action: async (payload, ctx) => {
|
|
await App.Cache.clearActorScope(payload.userId);
|
|
|
|
ctx.emit(APP_EVENT_PRIVATE_STATE_CLEARED, {
|
|
userId: payload.userId
|
|
});
|
|
|
|
return orcaSuccess({
|
|
emits: [ORCA_TOKEN_CACHE_OK]
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
Aquí sí usamos `ctx.emit()` porque estamos dentro de una action y queremos preservar trace.
|
|
|
|
Entonces hay dos tipos de emisión:
|
|
|
|
```txt
|
|
bus.publish()
|
|
Para módulos, componentes, servidor, código externo a orca.
|
|
Crea evento raíz desde la perspectiva de orca.
|
|
|
|
ctx.emit()
|
|
Solo dentro de una OrcaAction.
|
|
Crea evento hijo dentro del trace actual.
|
|
```
|
|
|
|
Si un módulo emite durante una acción, por ejemplo:
|
|
|
|
```ts
|
|
action: async () => {
|
|
await App.Auth.changeUser(nextUser); // Auth internamente hace bus.publish
|
|
}
|
|
```
|
|
|
|
`orca` lo verá como evento publicado durante un run, pero no sabrá con tanta precisión qué acción lo emitió salvo que tenga un contexto activo global interno. Hay dos opciones:
|
|
|
|
**Opción A, simple y limpia v0:**
|
|
|
|
```txt
|
|
Módulo publica por bus.
|
|
Orca detecta que hay run activo y lo encola como child event genérico.
|
|
parentRunId sí, emittedByAction quizá no.
|
|
```
|
|
|
|
**Opción B, más precisa:**
|
|
|
|
`orca` ejecuta cada action dentro de un “current action scope” interno. Si durante esa ejecución algún módulo hace `bus.publish`, `orca` intercepta el publish y lo marca como:
|
|
|
|
```ts
|
|
emittedByAction: ORCA_ACTION_CLEAR_PRIVATE_STATE
|
|
```
|
|
|
|
Eso requiere integración fuerte entre `buss` y `orca`.
|
|
|
|
Mi recomendación:
|
|
|
|
```txt
|
|
v0:
|
|
módulos siempre bus.publish
|
|
actions pueden ctx.emit
|
|
si un módulo publica durante un run, orca lo trata como evento derivado del run si puede detectarlo, pero no exigimos emittedByAction perfecto
|
|
|
|
v1:
|
|
integración bus/orca con current action scope para trazabilidad completa
|
|
```
|
|
|
|
Regla para desarrolladores:
|
|
|
|
```txt
|
|
Si estás escribiendo código de módulo: usa bus.publish.
|
|
Si estás escribiendo una OrcaAction: usa ctx.emit para eventos derivados del flujo.
|
|
```
|
|
|
|
Así evitamos acoplar módulos a `orca`, pero damos a las acciones una vía correcta para preservar trazabilidad.
|
|
|
|
|