[PR #24] [MERGED] feat: middleware withTraceEvents #39

Closed
opened 2026-02-16 03:16:00 -05:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/run-llama/workflows-ts/pull/24
Author: @himself65
Created: 4/5/2025
Status: Merged
Merged: 4/6/2025
Merged by: @himself65

Base: mainHead: trace-events


📝 Commits (10+)

📊 Changes

17 files changed (+696 additions, -120 deletions)

View changed files

📝 README.md (+124 -1)
📝 packages/fluere/src/core/event.ts (+0 -2)
📝 packages/fluere/src/core/internal/context.ts (+38 -9)
📝 packages/fluere/src/core/internal/executor.ts (+33 -25)
📝 packages/fluere/src/core/workflow.ts (+23 -35)
📝 packages/fluere/src/middleware/store.ts (+36 -12)
packages/fluere/src/middleware/trace-events.ts (+152 -0)
packages/fluere/src/middleware/trace-events/create-handler-decorator.ts (+46 -0)
packages/fluere/src/middleware/trace-events/run-once.ts (+10 -0)
📝 packages/fluere/src/middleware/validation.ts (+11 -14)
📝 packages/fluere/src/util/zod.ts (+10 -11)
packages/fluere/tests/full-workflow.spec.ts (+79 -0)
packages/fluere/tests/with-trace-events.spec.ts (+110 -0)
📝 packages/fluere/tests/with-validation.spec.ts (+17 -5)
📝 packages/fluere/tsconfig.browser.json (+4 -1)
📝 packages/fluere/tsconfig.json (+2 -2)
📝 vitest.workspace.ts (+1 -3)

📄 Description

withTraceEvents

Adds tracing capabilities to your workflow, allowing you to monitor/decorate handler and debug event flows easily.

When enabled,
it collects events based on the directed graph of the runtime and provide lifecycle hooks for each handler.

import { withTraceEvents, runOnce } from "fluere/middleware/trace-events";

const workflow = withTraceEvents(createWorkflow());

workflow.handle(
  [messageEvent],
  runOnce(() => {
    console.log("This message handler will only run once");
  }),
);

workflow.handle([startEvent], () => {
  getContext().sendEvent(messageEvent.with());
  getContext().sendEvent(messageEvent.with());
});

{
  const { sendEvent } = workflow.createContext();
  sendEvent(startEvent.with());
  sendEvent(messageEvent.with());
  // This message handler will only run once!
}
{
  const { sendEvent } = workflow.createContext();
  // For each new context, the decorator is isolated.
  sendEvent(startEvent.with());
  sendEvent(messageEvent.with());
  // This message handler will only run once!
}

createHandlerDecorator

You can create your own handler decorator to modify the behavior of the handler.

import { createHandlerDecorator } from "fluere/middleware/trace-events";

const noop: (...args: any[]) => void = function noop() {};
export const runOnce = createHandlerDecorator({
  debugLabel: "onceHook",
  getInitialValue: () => false,
  onBeforeHandler: (handler, handlerContext, tracked) =>
    tracked ? noop : handler,
  onAfterHandler: () => true,
});

HandlerContext

The HandlerContext includes the runtime information of the handler in the directed graph of the workflow.

type BaseHandlerContext = {
  // ... some other properties are hidden
  handler: Handler<WorkflowEvent<any>[], any>;
  inputEvents: WorkflowEvent<any>[];
  // events data that are accepted by the handler
  inputs: WorkflowEventData<any>[];
  // events data that are emitted by the handler
  outputs: WorkflowEventData<any>[];

  //#region linked list data structure
  prev: HandlerContext;
  next: Set<HandlerContext>;
  root: HandlerContext;
  //#endregion
};

type SyncHandlerContext = BaseHandlerContext & {
  async: false;
  pending: null;
};

type AsyncHandlerContext = BaseHandlerContext & {
  async: true;
  pending: Promise<WorkflowEventData<any> | void> | null;
};

type HandlerContext = AsyncHandlerContext | SyncHandlerContext;

For example, when you send two startEvent events, and send messageEvent twice (once in the handler and once in the global),
the HandlerContext from root to leaf is:

let once = false;
workflow.handle([startEvent], () => {
  const { sendEvent } = getContext();
  if (once) {
    return;
  }
  once = true;
  sendEvent(messageEvent.with());
});
const { sendEvent } = workflow.createContext();
sendEvent(startEvent.with());
sendEvent(startEvent.with());
sendEvent(messageEvent.with());
rootHandlerContext(0)
  ├── startEventContext(0)
  │   └── messageEventContext(0)
  ├── startEventContext(1)
  └── messageEventContext(1)

You can use any directed graph library to visualize the directed graph of the workflow.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/run-llama/workflows-ts/pull/24 **Author:** [@himself65](https://github.com/himself65) **Created:** 4/5/2025 **Status:** ✅ Merged **Merged:** 4/6/2025 **Merged by:** [@himself65](https://github.com/himself65) **Base:** `main` ← **Head:** `trace-events` --- ### 📝 Commits (10+) - [`e1002dd`](https://github.com/run-llama/workflows-ts/commit/e1002ddafc20cebc03b9a2e555301ad70123287c) feat: middleware `withTraceEvents` - [`738339d`](https://github.com/run-llama/workflows-ts/commit/738339dd5dc3a4b434b945c1facd98c66c271be3) fix: naming - [`6a7c455`](https://github.com/run-llama/workflows-ts/commit/6a7c4557f5462a830aa994a4cc7f81969aede919) fix: naming - [`d5a9584`](https://github.com/run-llama/workflows-ts/commit/d5a9584697d6ed30188b28ad070c2ad0dc44dd06) docs: update README.md - [`bfb53fa`](https://github.com/run-llama/workflows-ts/commit/bfb53fa09acef02f21ced3f2d73532f9cb1d3296) docs: update README.md - [`08d0ebc`](https://github.com/run-llama/workflows-ts/commit/08d0ebc790cd698c0b632eaf04bbce93fc441976) test: add full example - [`f40eae3`](https://github.com/run-llama/workflows-ts/commit/f40eae34215a3c94ea2d4679bf0498d2a1b4800e) test: fix - [`b9f7247`](https://github.com/run-llama/workflows-ts/commit/b9f7247705e8c9bb51c75e74d5ecf1c11808316f) doc: update - [`f443228`](https://github.com/run-llama/workflows-ts/commit/f4432281e5a702f54a6000530f874d861450180d) fix: log - [`6abf194`](https://github.com/run-llama/workflows-ts/commit/6abf194c907560cf1f4ccfb407355d0e745037fe) fix: doc ### 📊 Changes **17 files changed** (+696 additions, -120 deletions) <details> <summary>View changed files</summary> 📝 `README.md` (+124 -1) 📝 `packages/fluere/src/core/event.ts` (+0 -2) 📝 `packages/fluere/src/core/internal/context.ts` (+38 -9) 📝 `packages/fluere/src/core/internal/executor.ts` (+33 -25) 📝 `packages/fluere/src/core/workflow.ts` (+23 -35) 📝 `packages/fluere/src/middleware/store.ts` (+36 -12) ➕ `packages/fluere/src/middleware/trace-events.ts` (+152 -0) ➕ `packages/fluere/src/middleware/trace-events/create-handler-decorator.ts` (+46 -0) ➕ `packages/fluere/src/middleware/trace-events/run-once.ts` (+10 -0) 📝 `packages/fluere/src/middleware/validation.ts` (+11 -14) 📝 `packages/fluere/src/util/zod.ts` (+10 -11) ➕ `packages/fluere/tests/full-workflow.spec.ts` (+79 -0) ➕ `packages/fluere/tests/with-trace-events.spec.ts` (+110 -0) 📝 `packages/fluere/tests/with-validation.spec.ts` (+17 -5) 📝 `packages/fluere/tsconfig.browser.json` (+4 -1) 📝 `packages/fluere/tsconfig.json` (+2 -2) 📝 `vitest.workspace.ts` (+1 -3) </details> ### 📄 Description ### `withTraceEvents` Adds tracing capabilities to your workflow, allowing you to monitor/decorate handler and debug event flows easily. When enabled, it collects events based on the directed graph of the runtime and provide lifecycle hooks for each handler. ```ts import { withTraceEvents, runOnce } from "fluere/middleware/trace-events"; const workflow = withTraceEvents(createWorkflow()); workflow.handle( [messageEvent], runOnce(() => { console.log("This message handler will only run once"); }), ); workflow.handle([startEvent], () => { getContext().sendEvent(messageEvent.with()); getContext().sendEvent(messageEvent.with()); }); { const { sendEvent } = workflow.createContext(); sendEvent(startEvent.with()); sendEvent(messageEvent.with()); // This message handler will only run once! } { const { sendEvent } = workflow.createContext(); // For each new context, the decorator is isolated. sendEvent(startEvent.with()); sendEvent(messageEvent.with()); // This message handler will only run once! } ``` #### `createHandlerDecorator` You can create your own handler decorator to modify the behavior of the handler. ```ts import { createHandlerDecorator } from "fluere/middleware/trace-events"; const noop: (...args: any[]) => void = function noop() {}; export const runOnce = createHandlerDecorator({ debugLabel: "onceHook", getInitialValue: () => false, onBeforeHandler: (handler, handlerContext, tracked) => tracked ? noop : handler, onAfterHandler: () => true, }); ``` #### `HandlerContext` The `HandlerContext` includes the runtime information of the handler in the directed graph of the workflow. ```ts type BaseHandlerContext = { // ... some other properties are hidden handler: Handler<WorkflowEvent<any>[], any>; inputEvents: WorkflowEvent<any>[]; // events data that are accepted by the handler inputs: WorkflowEventData<any>[]; // events data that are emitted by the handler outputs: WorkflowEventData<any>[]; //#region linked list data structure prev: HandlerContext; next: Set<HandlerContext>; root: HandlerContext; //#endregion }; type SyncHandlerContext = BaseHandlerContext & { async: false; pending: null; }; type AsyncHandlerContext = BaseHandlerContext & { async: true; pending: Promise<WorkflowEventData<any> | void> | null; }; type HandlerContext = AsyncHandlerContext | SyncHandlerContext; ``` For example, when you send two `startEvent` events, and send `messageEvent` twice (once in the handler and once in the global), the `HandlerContext` from root to leaf is: ```ts let once = false; workflow.handle([startEvent], () => { const { sendEvent } = getContext(); if (once) { return; } once = true; sendEvent(messageEvent.with()); }); const { sendEvent } = workflow.createContext(); sendEvent(startEvent.with()); sendEvent(startEvent.with()); sendEvent(messageEvent.with()); ``` ``` rootHandlerContext(0) ├── startEventContext(0) │ └── messageEventContext(0) ├── startEventContext(1) └── messageEventContext(1) ``` You can use any directed graph library to visualize the directed graph of the workflow. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-02-16 03:16:00 -05:00
yindo closed this issue 2026-02-16 03:16:00 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: run-llama/workflows-ts#39