From 3f66755120c77eb20e21af0346e07ed39f18342c Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Tue, 28 Oct 2025 14:12:33 +0300 Subject: [PATCH 1/8] Create eventClient.ts --- src/core/eventClient.ts | 196 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/core/eventClient.ts diff --git a/src/core/eventClient.ts b/src/core/eventClient.ts new file mode 100644 index 00000000..95536943 --- /dev/null +++ b/src/core/eventClient.ts @@ -0,0 +1,196 @@ +import { EventResource } from "../common/open-api/sdk.gen"; +import { Client } from "../common/open-api/client"; +import { handleSdkError } from "./helpers"; +import type { EventHandler } from "../common"; +import { + GetQueueNamesResponses, + HandleIncomingEventData, +} from "../common/open-api/types.gen"; + +export class EventClient { + public readonly _client: Client; + + constructor(client: Client) { + this._client = client; + } + + /** + * Get all the event handlers + * + * @returns + */ + public async getAllEventHandlers(): Promise { + try { + const { data } = await EventResource.getEventHandlers({ + client: this._client, + throwOnError: true, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get all event handlers`); + } + } + + /** + * Add event handlers + * + * @param eventHandlers + * @returns + */ + public async addEventHandlers(eventHandlers: EventHandler[]): Promise { + try { + await EventResource.addEventHandler({ + body: eventHandlers, + client: this._client, + throwOnError: true, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to add event handlers`); + } + } + + /** + * Add an event handler + * + * @param eventHandler + * @returns + */ + public async addEventHandler(eventHandler: EventHandler): Promise { + try { + await EventResource.addEventHandler({ + body: [eventHandler], + client: this._client, + throwOnError: true, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to add event handler`); + } + } + + /** + * Update an event handler + * + * @param eventHandler + * @returns + */ + public async updateEventHandler(eventHandler: EventHandler): Promise { + try { + await EventResource.updateEventHandler({ + body: eventHandler, + client: this._client, + throwOnError: true, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to update event handler`); + } + } + + /** + * Handle an incoming event + * + * @param data + * @returns + */ + public async handleIncomingEvent( + data: { [key: string]: string } // TODO: add better data type after openapi spec update? + ): Promise { + try { + await EventResource.handleIncomingEvent({ + body: data, + client: this._client, + throwOnError: true, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to handle incoming event`); + } + } + + /** + * Get an event handler by name + * + * @param eventHandlerName + * @returns + */ + public async getEventHandlerByName( + eventHandlerName: string + ): Promise { + try { + const { data } = await EventResource.getEventHandlerByName({ + client: this._client, + throwOnError: true, + path: { name: eventHandlerName }, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get event handler by name`); + } + } + + /** + * Get all queue configs + * + * @returns + */ + public async getAllQueueConfigs(): Promise<{ [key: string]: string }> { + // TODO: add better return type after openapi spec update? + try { + const { data } = await EventResource.getQueueNames({ + client: this._client, + throwOnError: true, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get all queue configs`); + } + } + + /** + * Delete queue config + * + * @param queueType + * @param queueName + * @returns + */ + public async deleteQueueConfig( + queueType: string, + queueName: string + ): Promise { + try { + await EventResource.deleteQueueConfig({ + path: { queueType, queueName }, + client: this._client, + throwOnError: true, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to delete queue config`); + } + } + + /** + * Get queue config + * + * @param queueType + * @param queueName + * @returns + */ + public async getQueueConfig( + queueType: string, + queueName: string + ): Promise<{ [key: string]: unknown }> { + // TODO: add better return type after openapi spec update? + try { + const { data } = await EventResource.getQueueConfig({ + path: { queueType, queueName }, + client: this._client, + throwOnError: true, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get queue config`); + } + } +} From 92214c56d2269bd3250eb0d548e2e7e7ded7e344 Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Thu, 30 Oct 2025 16:00:39 +0300 Subject: [PATCH 2/8] Update eventClient.ts --- src/core/eventClient.ts | 342 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 308 insertions(+), 34 deletions(-) diff --git a/src/core/eventClient.ts b/src/core/eventClient.ts index 95536943..82fc736f 100644 --- a/src/core/eventClient.ts +++ b/src/core/eventClient.ts @@ -1,10 +1,18 @@ -import { EventResource } from "../common/open-api/sdk.gen"; +import { + EventResource, + EventExecutionResource, + EventMessageResource, +} from "../common/open-api/sdk.gen"; import { Client } from "../common/open-api/client"; import { handleSdkError } from "./helpers"; import type { EventHandler } from "../common"; import { - GetQueueNamesResponses, - HandleIncomingEventData, + ExtendedEventExecution, + EventMessage, + SearchResultHandledEventResponse, + Tag, + ConnectivityTestInput, + ConnectivityTestResult, } from "../common/open-api/types.gen"; export class EventClient { @@ -16,8 +24,8 @@ export class EventClient { /** * Get all the event handlers - * - * @returns + * @returns {Promise} + * @throws {ConductorSdkError} */ public async getAllEventHandlers(): Promise { try { @@ -34,9 +42,9 @@ export class EventClient { /** * Add event handlers - * - * @param eventHandlers - * @returns + * @param {EventHandler[]} eventHandlers + * @returns {Promise} + * @throws {ConductorSdkError} */ public async addEventHandlers(eventHandlers: EventHandler[]): Promise { try { @@ -52,9 +60,9 @@ export class EventClient { /** * Add an event handler - * - * @param eventHandler - * @returns + * @param {EventHandler} eventHandler + * @returns {Promise} + * @throws {ConductorSdkError} */ public async addEventHandler(eventHandler: EventHandler): Promise { try { @@ -70,9 +78,9 @@ export class EventClient { /** * Update an event handler - * - * @param eventHandler - * @returns + * @param {EventHandler} eventHandler + * @returns {Promise} + * @throws {ConductorSdkError} */ public async updateEventHandler(eventHandler: EventHandler): Promise { try { @@ -88,12 +96,12 @@ export class EventClient { /** * Handle an incoming event - * - * @param data - * @returns + * @param {Record} data + * @returns {Promise} + * @throws {ConductorSdkError} */ public async handleIncomingEvent( - data: { [key: string]: string } // TODO: add better data type after openapi spec update? + data: Record ): Promise { try { await EventResource.handleIncomingEvent({ @@ -108,9 +116,9 @@ export class EventClient { /** * Get an event handler by name - * - * @param eventHandlerName - * @returns + * @param {string} eventHandlerName + * @returns {Promise} + * @throws {ConductorSdkError} */ public async getEventHandlerByName( eventHandlerName: string @@ -130,11 +138,10 @@ export class EventClient { /** * Get all queue configs - * - * @returns + * @returns {Promise>} + * @throws {ConductorSdkError} */ public async getAllQueueConfigs(): Promise<{ [key: string]: string }> { - // TODO: add better return type after openapi spec update? try { const { data } = await EventResource.getQueueNames({ client: this._client, @@ -149,10 +156,10 @@ export class EventClient { /** * Delete queue config - * - * @param queueType - * @param queueName - * @returns + * @param {string} queueType + * @param {string} queueName + * @returns {Promise} + * @throws {ConductorSdkError} */ public async deleteQueueConfig( queueType: string, @@ -171,16 +178,15 @@ export class EventClient { /** * Get queue config - * - * @param queueType - * @param queueName - * @returns + * @param {string} queueType + * @param {string} queueName + * @returns {Promise>} + * @throws {ConductorSdkError} */ public async getQueueConfig( queueType: string, queueName: string - ): Promise<{ [key: string]: unknown }> { - // TODO: add better return type after openapi spec update? + ): Promise> { try { const { data } = await EventResource.getQueueConfig({ path: { queueType, queueName }, @@ -193,4 +199,272 @@ export class EventClient { handleSdkError(error, `Failed to get queue config`); } } + + /** + * Get event handlers for a given event + * @param {string} event + * @param {boolean} [activeOnly=false] Only return active handlers. + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async getEventHandlersForEvent( + event: string, + activeOnly = false + ): Promise { + try { + const { data } = await EventResource.getEventHandlersForEvent({ + client: this._client, + throwOnError: true, + path: { event }, + query: { activeOnly }, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get event handlers for event: ${event}`); + } + } + + /** + * Remove an event handler by name + * @param {string} name + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async removeEventHandler(name: string): Promise { + try { + await EventResource.removeEventHandlerStatus({ + client: this._client, + throwOnError: true, + path: { name }, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to remove event handler: ${name}`); + } + } + + /** + * Get tags for an event handler + * @param {string} name + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async getTagsForEventHandler(name: string): Promise { + try { + const { data } = await EventResource.getTagsForEventHandler({ + client: this._client, + throwOnError: true, + path: { name }, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get tags for event handler: ${name}`); + } + } + + /** + * Put tags for an event handler + * @param {string} name + * @param {Tag[]} tags + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async putTagForEventHandler(name: string, tags: Tag[]): Promise { + try { + await EventResource.putTagForEventHandler({ + client: this._client, + throwOnError: true, + path: { name }, + body: tags, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to put tags for event handler: ${name}`); + } + } + + /** + * Delete tags for an event handler + * @param {string} name + * @param {Tag[]} tags + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async deleteTagForEventHandler( + name: string, + tags: Tag[] + ): Promise { + try { + await EventResource.deleteTagForEventHandler({ + client: this._client, + throwOnError: true, + path: { name }, + body: tags, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to delete tags for event handler: ${name}`); + } + } + + /** + * Test connectivity for a given queue using a workflow with EVENT task and an EventHandler + * @param {ConnectivityTestInput} input + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async testConnectivity( + input: ConnectivityTestInput + ): Promise { + try { + const { data } = await EventResource.testConnectivity({ + client: this._client, + throwOnError: true, + body: input, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to test connectivity`); + } + } + + /** + * Create or update queue config by name + * @deprecated Prefer server's newer endpoints if available + * @param {string} queueType + * @param {string} queueName + * @param {string} config + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async putQueueConfig( + queueType: string, + queueName: string, + config: string + ): Promise { + try { + await EventResource.putQueueConfig({ + client: this._client, + throwOnError: true, + path: { queueType, queueName }, + body: config, + }); + } catch (error: unknown) { + handleSdkError(error, `Failed to put queue config`); + } + } + + /** + * Test endpoint (as exposed by API) + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async test(): Promise { + try { + const { data } = await EventResource.test({ + client: this._client, + throwOnError: true, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to call test endpoint`); + } + } + + /** + * Get all active event handlers (execution view) + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async getAllActiveEventHandlers(): Promise { + try { + const { data } = await EventExecutionResource.getEventHandlersForEvent1({ + client: this._client, + throwOnError: true, + }); + + return data; + } catch (error: unknown) { + handleSdkError( + error, + `Failed to get all active event handlers (execution view)` + ); + } + } + + /** + * Get event executions for a specific handler + * @param {string} eventHandlerName + * @param {number} [from] Pagination cursor + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async getEventExecutions( + eventHandlerName: string, + from?: number + ): Promise { + try { + const { data } = await EventExecutionResource.getEventHandlersForEvent2({ + client: this._client, + throwOnError: true, + path: { eventHandlerName }, + query: { from }, + }); + + return data; + } catch (error: unknown) { + handleSdkError( + error, + `Failed to get event executions for handler: ${eventHandlerName}` + ); + } + } + + /** + * Get all event handlers with statistics (messages view) + * @param {number} [from] Pagination cursor + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async getEventHandlersWithStats( + from?: number + ): Promise { + try { + const { data } = await EventMessageResource.getEvents({ + client: this._client, + throwOnError: true, + query: { from }, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get event handlers statistics`); + } + } + + /** + * Get event messages for a given event + * @param {string} event + * @param {number} [from] Pagination cursor + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async getEventMessages( + event: string, + from?: number + ): Promise { + try { + const { data } = await EventMessageResource.getMessages({ + client: this._client, + throwOnError: true, + path: { event }, + query: { from }, + }); + + return data; + } catch (error: unknown) { + handleSdkError(error, `Failed to get event messages for event: ${event}`); + } + } } From ef6a1495f192e77bd0c37837c72f30da7fdb56b0 Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Tue, 4 Nov 2025 00:00:44 +0300 Subject: [PATCH 3/8] add EventClient tests, handle empty response errors, remove outdated resource tests --- integration-tests/common/EventClient.test.ts | 759 ++++++++++++++++++ .../common/EventResourceService.test.ts | 55 -- src/core/eventClient.ts | 49 +- src/core/index.ts | 1 + 4 files changed, 804 insertions(+), 60 deletions(-) create mode 100644 integration-tests/common/EventClient.test.ts delete mode 100644 integration-tests/common/EventResourceService.test.ts diff --git a/integration-tests/common/EventClient.test.ts b/integration-tests/common/EventClient.test.ts new file mode 100644 index 00000000..92dd3e9e --- /dev/null +++ b/integration-tests/common/EventClient.test.ts @@ -0,0 +1,759 @@ +import { expect, describe, test, jest } from "@jest/globals"; +import { orkesConductorClient } from "../../src/orkes"; +import { EventClient } from "../../src/core"; +import type { EventHandler } from "../../src/common"; +import type { + Tag, + ConnectivityTestInput, + Action, +} from "../../src/common/open-api/types.gen"; +import { IntegrationResource } from "../../src/common/open-api/sdk.gen"; + +describe("EventClient", () => { + jest.setTimeout(60000); + // Helper function to create unique names + const createUniqueName = (prefix: string) => + `jsSdkTest:${prefix}:${Date.now()}`; + + // Helper function to create a test event handler + const createEventHandler = ( + name: string, + event: string, + active = true + ): EventHandler => ({ + name, + event, + active, + actions: [], + description: `Test event handler: ${name}`, + }); + + describe("Event Handler Management", () => { + test("Should add a single event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: { + testKey: "testValue", + }, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test event handler: ${handlerName}`, + condition: "true", + evaluatorType: "javascript", + tags: [ + { key: "test-tag-key-1", value: "test-tag-value-1" }, + { key: "environment", value: "test" }, + ], + }; + + await expect( + eventClient.addEventHandler(eventHandler) + ).resolves.not.toThrow(); + + const retrievedHandler = await eventClient.getEventHandlerByName( + handlerName + ); + expect(retrievedHandler.name).toEqual(handlerName); + expect(retrievedHandler.event).toEqual(eventName); + expect(retrievedHandler.active).toEqual(true); + expect(retrievedHandler.description).toEqual(eventHandler.description); + expect(retrievedHandler.condition).toEqual(eventHandler.condition); + expect(retrievedHandler.evaluatorType).toEqual( + eventHandler.evaluatorType + ); + expect(retrievedHandler.createdBy?.includes("app:")).toBeTruthy(); + expect(typeof retrievedHandler.createdBy).toBe("string"); + expect(retrievedHandler.actions).toBeDefined(); + expect(Array.isArray(retrievedHandler.actions)).toBe(true); + expect(retrievedHandler.actions?.length).toBeGreaterThanOrEqual(1); + const retrievedAction = retrievedHandler.actions![0]; + expect(retrievedAction.action).toEqual("start_workflow"); + expect(retrievedAction.start_workflow).toBeDefined(); + expect(retrievedAction.start_workflow?.name).toEqual(workflowName); + expect(retrievedAction.start_workflow?.version).toEqual(1); + + expect(retrievedHandler.tags).toBeDefined(); + expect(Array.isArray(retrievedHandler.tags)).toBe(true); + expect(retrievedHandler.tags?.length).toBeGreaterThanOrEqual(2); + eventHandler.tags?.forEach((tag) => { + const foundTag = retrievedHandler.tags!.find( + (t) => t.key === tag.key && t.value === tag.value + ); + expect(foundTag).toBeDefined(); + }); + + // Cleanup + await eventClient.removeEventHandler(handlerName); + }); + + test("Should add multiple event handlers", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName1 = createUniqueName("event-handler-1"); + const handlerName2 = createUniqueName("event-handler-2"); + const eventName1 = createUniqueName("event-1"); + const eventName2 = createUniqueName("event-2"); + + const eventHandlers = [ + createEventHandler(handlerName1, eventName1), + createEventHandler(handlerName2, eventName2), + ]; + + await expect( + eventClient.addEventHandlers(eventHandlers) + ).resolves.not.toThrow(); + + // Verify both were added + const allHandlers = await eventClient.getAllEventHandlers(); + const addedHandlers = allHandlers.filter( + (h) => h.name === handlerName1 || h.name === handlerName2 + ); + expect(addedHandlers.length).toBeGreaterThanOrEqual(2); + + // Cleanup + await eventClient.removeEventHandler(handlerName1); + await eventClient.removeEventHandler(handlerName2); + }); + + test("Should update an event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const eventHandler = createEventHandler(handlerName, eventName, true); + + // Add the handler + await eventClient.addEventHandler(eventHandler); + + // Update the handler + const updatedHandler: EventHandler = { + ...eventHandler, + active: false, + description: "Updated description", + }; + + await expect( + eventClient.updateEventHandler(updatedHandler) + ).resolves.not.toThrow(); + + // Verify it was updated + const retrievedHandler = await eventClient.getEventHandlerByName( + handlerName + ); + expect(retrievedHandler.active).toEqual(false); + expect(retrievedHandler.description).toEqual("Updated description"); + + // Cleanup + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get all event handlers", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlers = await eventClient.getAllEventHandlers(); + + expect(Array.isArray(handlers)).toBe(true); + }); + + test("Should get event handler by name", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const eventHandler = createEventHandler(handlerName, eventName); + + await eventClient.addEventHandler(eventHandler); + + const retrievedHandler = await eventClient.getEventHandlerByName( + handlerName + ); + + expect(retrievedHandler.name).toEqual(handlerName); + expect(retrievedHandler.event).toEqual(eventName); + + // Cleanup + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get event handlers for a specific event", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const eventHandler = createEventHandler(handlerName, eventName); + + await eventClient.addEventHandler(eventHandler); + + const handlers = await eventClient.getEventHandlersForEvent(eventName); + + expect(Array.isArray(handlers)).toBe(true); + const foundHandler = handlers.find((h) => h.name === handlerName); + expect(foundHandler).toBeDefined(); + + // Test with activeOnly parameter + const activeHandlers = await eventClient.getEventHandlersForEvent( + eventName, + true + ); + expect(Array.isArray(activeHandlers)).toBe(true); + + // Cleanup + await eventClient.removeEventHandler(handlerName); + }); + + test("Should remove an event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const eventHandler = createEventHandler(handlerName, eventName); + + await eventClient.addEventHandler(eventHandler); + + // Verify it exists + const retrievedHandler = await eventClient.getEventHandlerByName( + handlerName + ); + expect(retrievedHandler.name).toEqual(handlerName); + + // Remove it + await expect( + eventClient.removeEventHandler(handlerName) + ).resolves.not.toThrow(); + + // Verify it's removed by checking handlers for the event + const handlers = await eventClient.getEventHandlersForEvent(eventName); + const foundHandler = handlers.find((h) => h.name === handlerName); + expect(foundHandler).toBeUndefined(); + }); + }); + + describe("Tag Management", () => { + test("Should get tags for an event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const eventHandler = createEventHandler(handlerName, eventName); + + await eventClient.addEventHandler(eventHandler); + + // Add tags to the event handler + const expectedTags: Tag[] = [ + { key: "test-key-1", value: "test-value-1" }, + { key: "test-key-2", value: "test-value-2" }, + { key: "environment", value: "test" }, + ]; + + await eventClient.putTagForEventHandler(handlerName, expectedTags); + + // Get tags and verify they match exactly + const retrievedTags = await eventClient.getTagsForEventHandler( + handlerName + ); + + expect(Array.isArray(retrievedTags)).toBe(true); + expect(retrievedTags.length).toBeGreaterThanOrEqual(expectedTags.length); + + // Verify each expected tag is present + expectedTags.forEach((expectedTag) => { + const foundTag = retrievedTags.find( + (tag) => + tag.key === expectedTag.key && tag.value === expectedTag.value + ); + expect(foundTag).toBeDefined(); + expect(foundTag?.key).toEqual(expectedTag.key); + expect(foundTag?.value).toEqual(expectedTag.value); + }); + + // Cleanup + await eventClient.removeEventHandler(handlerName); + }); + + test("Should put tags for an event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const eventHandler = createEventHandler(handlerName, eventName); + + await eventClient.addEventHandler(eventHandler); + + const tags: Tag[] = [ + { key: "test-key-1", value: "test-value-1" }, + { key: "test-key-2", value: "test-value-2" }, + { key: "category", value: "integration-test" }, + ]; + + await expect( + eventClient.putTagForEventHandler(handlerName, tags) + ).resolves.not.toThrow(); + + // Verify the tags were actually added + const retrievedTags = await eventClient.getTagsForEventHandler( + handlerName + ); + + expect(Array.isArray(retrievedTags)).toBe(true); + expect(retrievedTags.length).toBeGreaterThanOrEqual(tags.length); + + // Verify each added tag is present with correct key and value + tags.forEach((addedTag) => { + const foundTag = retrievedTags.find( + (tag) => tag.key === addedTag.key && tag.value === addedTag.value + ); + expect(foundTag).toBeDefined(); + expect(foundTag?.key).toEqual(addedTag.key); + expect(foundTag?.value).toEqual(addedTag.value); + }); + + // Cleanup + await eventClient.removeEventHandler(handlerName); + }); + + test("Should delete tags for an event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const eventHandler = createEventHandler(handlerName, eventName); + + await eventClient.addEventHandler(eventHandler); + + const tags: Tag[] = [ + { key: "test-key-1", value: "test-value-1" }, + { key: "test-key-2", value: "test-value-2" }, + ]; + + // First add all tags + await eventClient.putTagForEventHandler(handlerName, tags); + + // Verify all tags are present before deletion + const tagsBeforeDeletion = await eventClient.getTagsForEventHandler( + handlerName + ); + tags.forEach((tag) => { + const foundTag = tagsBeforeDeletion.find( + (t) => t.key === tag.key && t.value === tag.value + ); + expect(foundTag).toBeDefined(); + }); + + // Delete one specific tag + const tagToDelete: Tag = tags[0]; + const remainingTag: Tag = tags[1]; + + await expect( + eventClient.deleteTagForEventHandler(handlerName, tagToDelete) + ).resolves.not.toThrow(); + + // Verify the deleted tag is no longer present + const tagsAfterDeletion = await eventClient.getTagsForEventHandler( + handlerName + ); + const foundDeletedTag = tagsAfterDeletion.find( + (tag) => tag.key === tagToDelete.key && tag.value === tagToDelete.value + ); + expect(foundDeletedTag).toBeUndefined(); + + // Verify the remaining tags are still present + const foundRemainingTag = tagsAfterDeletion.find( + (tag) => + tag.key === remainingTag.key && tag.value === remainingTag.value + ); + expect(foundRemainingTag).toBeDefined(); + expect(foundRemainingTag?.key).toEqual(remainingTag.key); + expect(foundRemainingTag?.value).toEqual(remainingTag.value); + + // Cleanup + await eventClient.removeEventHandler(handlerName); + }); + }); + + describe("Event Processing", () => { + test("Should handle incoming event", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const workflowName = createUniqueName("test-workflow"); + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + const retrievedHandler = await eventClient.getEventHandlerByName( + handlerName + ); + expect(retrievedHandler.name).toEqual(handlerName); + expect(retrievedHandler.event).toEqual(eventName); + expect(retrievedHandler.active).toBe(true); + + const handlersForEvent = await eventClient.getEventHandlersForEvent( + eventName + ); + expect(handlersForEvent.length).toBeGreaterThan(0); + expect(handlersForEvent.some((h) => h.name === handlerName)).toBe(true); + + const eventData: Record = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + + await eventClient.handleIncomingEvent(eventData); + + const handlerAfterEvent = await eventClient.getEventHandlerByName( + handlerName + ); + expect(handlerAfterEvent.active).toBe(true); + expect(handlerAfterEvent.event).toEqual(eventName); + + const executions = await eventClient.getEventExecutions(handlerName); + expect(Array.isArray(executions)).toBe(true); + + const ourExecution = executions.find( + (exec) => exec.event === eventName || exec.name === handlerName + ); + expect(ourExecution).toBeDefined(); + if (ourExecution?.event) { + expect(ourExecution.event).toEqual(eventName); + } + if (ourExecution?.name) { + expect(ourExecution.name).toEqual(handlerName); + } + + await eventClient.removeEventHandler(handlerName); + }); + }); + + describe("Test Endpoint", () => { + test("Should call test endpoint", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + + const result = await eventClient.test(); + expect(result).toBeDefined(); + }); + }); + + describe("Event Executions and Statistics", () => { + test("Should get all active event handlers (execution view)", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + + await eventClient.addEventHandler(eventHandler); + + // Trigger an event so the handler processes it and appears in execution view + const eventData: Record = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + // Get all active event handlers (execution view only shows handlers with executions) + const result = await eventClient.getAllActiveEventHandlers(); + + expect(result).toBeDefined(); + expect(result).toHaveProperty("results"); + expect(Array.isArray(result.results)).toBe(true); + expect(result.results?.length).toBeGreaterThan(0); + + // Verify our handler appears in the results + const foundHandler = result.results?.find( + (h) => h.name === handlerName && h.event === eventName + ); + expect(foundHandler).toBeDefined(); + expect(foundHandler?.name).toBe(handlerName); + expect(foundHandler?.event).toBe(eventName); + expect(foundHandler?.active).toBe(true); + + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get event executions for a handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + // Trigger an event so the handler processes it and creates an execution + const eventData = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + const executions = await eventClient.getEventExecutions(handlerName, 0); + expect(Array.isArray(executions)).toBe(true); + expect(executions.length).toBeGreaterThan(0); + + // Find our execution in the results + const ourExecution = executions.find( + (exec) => exec.name === handlerName && exec.event === eventName + ); + expect(ourExecution).toBeDefined(); + expect(ourExecution?.name).toBe(handlerName); + expect(ourExecution?.event).toBe(eventName); + expect(typeof ourExecution?.id).toBe("string"); + expect(typeof ourExecution?.created).toBe("number"); + expect(ourExecution?.action).toBe("start_workflow"); + + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get event handlers with statistics", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + // Trigger an event so the handler processes it and creates an execution + const eventData = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + const response = await eventClient.getEventHandlersWithStats(0); + expect(response).toBeDefined(); + expect(response).toHaveProperty("results"); + expect(Array.isArray(response.results)).toBe(true); + expect(response.results?.length).toBeGreaterThan(0); + + // Find our handler in the results + const foundHandler = response.results?.find((h) => h.event === eventName); + expect(foundHandler).toBeDefined(); + expect(foundHandler?.event).toBe(eventName); + expect(foundHandler?.active).toBe(true); + expect(typeof foundHandler?.numberOfActions).toBe("number"); + expect(typeof foundHandler?.numberOfMessages).toBe("number"); + + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get event messages for an event", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + // Trigger an event to create messages + const eventData = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + const messages = await eventClient.getEventMessages(eventName); + expect(Array.isArray(messages)).toBe(true); + expect(messages.length).toBeGreaterThan(0); + + // Find our message in the results + const ourMessage = messages.find((msg) => msg.eventTarget === eventName); + expect(ourMessage).toBeDefined(); + expect(typeof ourMessage).toBe("object"); + expect(typeof ourMessage?.id).toBe("string"); + expect(typeof ourMessage?.createdAt).toBe("number"); + expect(ourMessage?.fullPayload?.event).toBe(eventName); + expect(ourMessage?.fullPayload?.source).toBe("integration-test"); + expect(ourMessage?.fullPayload?.message).toBe("test event"); + + await eventClient.removeEventHandler(handlerName); + }); + }); + + describe("Error Handling", () => { + test("Should throw error when getting non-existent event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const nonExistentName = createUniqueName("non-existent-handler"); + + await expect( + eventClient.getEventHandlerByName(nonExistentName) + ).rejects.toThrow(); + }); + + test("Should throw error when removing non-existent handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const nonExistentName = createUniqueName("non-existent-handler"); + + await expect( + eventClient.removeEventHandler(nonExistentName) + ).rejects.toThrow(); + }); + + test("Should throw error when updating non-existent event handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const nonExistentName = createUniqueName("non-existent-handler"); + const eventName = createUniqueName("event"); + + const nonExistentHandler: EventHandler = { + name: nonExistentName, + event: eventName, + active: true, + actions: [], + description: "Non-existent handler", + }; + + await expect( + eventClient.updateEventHandler(nonExistentHandler) + ).rejects.toThrow(); + }); + + test("Should throw error when getting queue config for non-existent queue", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const nonExistentQueueType = createUniqueName("non-existent-type"); + const nonExistentQueueName = createUniqueName("non-existent-queue"); + + await expect( + eventClient.getQueueConfig(nonExistentQueueType, nonExistentQueueName) + ).rejects.toThrow(); + }); + + test("Should throw error when adding event handler with invalid data", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + + const invalidHandler = { + name: "", + event: "", + active: true, + actions: [], + }; + + await expect( + eventClient.addEventHandler(invalidHandler) + ).rejects.toThrow(); + }); + + test("Should handle error when testing connectivity with invalid input", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + + const invalidInput: ConnectivityTestInput = { + sink: "", + input: {}, + }; + + await expect( + eventClient.testConnectivity(invalidInput) + ).rejects.toThrow(); + }); + + test("Should handle error when handling incoming event with invalid data", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + + const invalidEventData: Record = {}; + + await expect( + eventClient.handleIncomingEvent(invalidEventData) + ).rejects.toThrow(); + }); + }); +}); diff --git a/integration-tests/common/EventResourceService.test.ts b/integration-tests/common/EventResourceService.test.ts deleted file mode 100644 index 4b2b8dbd..00000000 --- a/integration-tests/common/EventResourceService.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { expect, describe, test } from "@jest/globals"; -import { orkesConductorClient } from "../../src/orkes"; -import { EventResource } from "../../src/common/open-api/sdk.gen"; - -describe("EventResourceService", () => { - test("Should create an event handler with description and tags and then delete it", async () => { - const client = await orkesConductorClient(); - const eventApi = EventResource; - - const now = Date.now(); - const [eventName, event, eventDescription, eventTagKey, eventTagValue] = [ - `jsSdkTest-EventName-${now}`, - `jsSdkTest:eventHandler:1${now}`, - "jsSdkTestDescription", - "jsSdkTestTagKey", - "jsSdkTestTagValue", - ]; - - const eventHandler = { - name: eventName, - event: event, - active: true, - actions: [], - description: eventDescription, - tags: [{ key: eventTagKey, value: eventTagValue }], - }; - - await eventApi.addEventHandler({ body: [eventHandler], client }); - const { data: eventHandlers } = await eventApi.getEventHandlersForEvent({ - path: { event }, - client, - }); - if (!eventHandlers) { - throw new Error("Event handlers not found"); - } - - expect(eventHandlers.length).toEqual(1); - expect(eventHandlers[0].description).toEqual(eventDescription); - expect(eventHandlers[0].tags).toEqual([ - { key: eventTagKey, value: eventTagValue }, - ]); - - await eventApi.removeEventHandlerStatus({ - path: { name: eventName }, - client, - }); - const { data: eventHandlersAfterRemove } = - await eventApi.getEventHandlersForEvent({ path: { event }, client }); - if (!eventHandlersAfterRemove) { - throw new Error("Event handlers not found"); - } - - expect(eventHandlersAfterRemove.length).toEqual(0); - }); -}); diff --git a/src/core/eventClient.ts b/src/core/eventClient.ts index 82fc736f..a9d734f6 100644 --- a/src/core/eventClient.ts +++ b/src/core/eventClient.ts @@ -124,15 +124,21 @@ export class EventClient { eventHandlerName: string ): Promise { try { - const { data } = await EventResource.getEventHandlerByName({ + const { response, data } = await EventResource.getEventHandlerByName({ client: this._client, throwOnError: true, path: { name: eventHandlerName }, }); + if (response.headers.get("content-length") === "0") { + throw new Error("Response is empty"); + } return data; } catch (error: unknown) { - handleSdkError(error, `Failed to get event handler by name`); + handleSdkError( + error, + `Failed to get event handler by name ${eventHandlerName}` + ); } } @@ -172,7 +178,10 @@ export class EventClient { throwOnError: true, }); } catch (error: unknown) { - handleSdkError(error, `Failed to delete queue config`); + handleSdkError( + error, + `Failed to delete queue config ${queueType} ${queueName}` + ); } } @@ -194,6 +203,10 @@ export class EventClient { throwOnError: true, }); + if (Object.keys(data).length === 0) { + throw new Error("Response is empty"); + } + return data; } catch (error: unknown) { handleSdkError(error, `Failed to get queue config`); @@ -290,7 +303,7 @@ export class EventClient { * @returns {Promise} * @throws {ConductorSdkError} */ - public async deleteTagForEventHandler( + public async deleteTagsForEventHandler( name: string, tags: Tag[] ): Promise { @@ -302,7 +315,33 @@ export class EventClient { body: tags, }); } catch (error: unknown) { - handleSdkError(error, `Failed to delete tags for event handler: ${name}`); + handleSdkError( + error, + `Failed to delete tags for an event handler: ${name}` + ); + } + } + + /** + * Delete a tag for an event handler + * @param {string} name + * @param {Tag} tag + * @returns {Promise} + * @throws {ConductorSdkError} + */ + public async deleteTagForEventHandler(name: string, tag: Tag): Promise { + try { + await EventResource.deleteTagForEventHandler({ + client: this._client, + throwOnError: true, + path: { name }, + body: [tag], + }); + } catch (error: unknown) { + handleSdkError( + error, + `Failed to delete a tag for an event handler: ${name}` + ); } } diff --git a/src/core/index.ts b/src/core/index.ts index 1d92f0c7..1a35d3bd 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -7,3 +7,4 @@ export * from "./schedulerClient"; export * from "./taskClient"; export * from "./templateClient"; export * from "./metadataClient"; +export * from "./EventClient"; \ No newline at end of file From b6721680ddfb1fd459a5a9f28b2ae773ed83ea2f Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Tue, 4 Nov 2025 18:38:15 +0300 Subject: [PATCH 4/8] add docs, make related types public --- README.md | 112 ++++ docs/api-reference/event-client.md | 811 +++++++++++++++++++++++++++++ src/common/index.ts | 6 + src/core/eventClient.ts | 6 +- 4 files changed, 932 insertions(+), 3 deletions(-) create mode 100644 docs/api-reference/event-client.md diff --git a/README.md b/README.md index 8b97f250..640c174b 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,14 @@ Show support for the Conductor OSS. Please help spread the awareness by starrin - [Step 1: Create a MetadataClient](#step-1-create-a-metadataclient) - [Step 2: Define and Register a Task](#step-2-define-and-register-a-task) - [Step 3: Define and Register a Workflow](#step-3-define-and-register-a-workflow) +- [Events](#events) + - [The EventClient](#the-eventclient) + - [Quick Start: Using Event Handlers](#quick-start-using-event-handlers) + - [Step 1: Create an EventClient](#step-1-create-an-eventclient) + - [Step 2: Register an Event Handler](#step-2-register-an-event-handler) + - [Step 3: Publish Events](#step-3-publish-events) + - [Step 4: Monitor Event Processing](#step-4-monitor-event-processing) + - [Step 5: Manage Event Handlers](#step-5-manage-event-handlers) - [Human Tasks](#human-tasks) - [The HumanExecutor and TemplateClient](#the-humanexecutor-and-templateclient) - [Quick Start: Creating and Managing a Human Task](#quick-start-creating-and-managing-a-human-task) @@ -716,6 +724,110 @@ await metadataClient.registerWorkflowDef(wf); For a complete method reference, see the [MetadataClient API Reference](docs/api-reference/metadata-client.md). +## Events + +Event handlers in Conductor allow you to automatically trigger actions (like starting workflows) when events are received. This enables event-driven workflows and integrations with external systems. + +### The EventClient + +The `EventClient` manages event handlers and event processing. For a complete method reference, see the [EventClient API Reference](docs/api-reference/event-client.md). + +### Quick Start: Using Event Handlers + +Here's how to set up event-driven workflows: + +#### Step 1: Create an EventClient + +First, create an instance of the `EventClient`: + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); +``` + +#### Step 2: Register an Event Handler + +Create an event handler that defines what action to take when an event is received. In this example, we'll start a workflow when an order is created: + +```typescript +await eventClient.addEventHandler({ + name: "order_created_handler", + event: "order.created", + active: true, + description: "Starts fulfillment workflow when order is created", + actions: [ + { + action: "start_workflow", + start_workflow: { + name: "fulfill_order", + version: 1, + input: { + orderId: "${event.orderId}", + customerId: "${event.customerId}", + }, + }, + }, + ], +}); +``` + +#### Step 3: Publish Events + +When an event occurs, publish it to Conductor. All active handlers registered for that event will be triggered: + +```typescript +await eventClient.handleIncomingEvent({ + event: "order.created", + orderId: "ORDER-123", + customerId: "CUST-456", + amount: "99.99", + timestamp: Date.now().toString(), +}); +``` + +#### Step 4: Monitor Event Processing + +You can monitor event handlers and their execution history: + +```typescript +// Get all handlers for a specific event +const handlers = await eventClient.getEventHandlersForEvent("order.created"); + +// Get execution history for a handler +const executions = await eventClient.getEventExecutions("order_created_handler"); + +// Get event messages +const messages = await eventClient.getEventMessages("order.created"); +``` + +#### Step 5: Manage Event Handlers + +Update, deactivate, or remove event handlers as needed: + +```typescript +// Update a handler +await eventClient.updateEventHandler({ + name: "order_created_handler", + active: false, // Deactivate + // ... other fields +}); + +// Remove a handler +await eventClient.removeEventHandler("order_created_handler"); +``` + +**Event Handler Actions:** + +Event handlers support various actions: +- `start_workflow` - Start a workflow execution +- `complete_task` - Complete a specific task +- `fail_task` - Fail a specific task +- `terminate_workflow` - Terminate a workflow +- `update_workflow_variables` - Update workflow variables + +For a complete method reference, see the [EventClient API Reference](docs/api-reference/event-client.md). + ## Human Tasks Human tasks integrate human interaction into your automated workflows. They pause a workflow until a person provides input, such as an approval, a correction, or additional information. diff --git a/docs/api-reference/event-client.md b/docs/api-reference/event-client.md new file mode 100644 index 00000000..c5c9f0b1 --- /dev/null +++ b/docs/api-reference/event-client.md @@ -0,0 +1,811 @@ +# EventClient API Reference + +The `EventClient` manages event handlers and event processing in Conductor. Event handlers allow you to automatically trigger actions (like starting workflows) when events are received. + +## Constructor + +### `new EventClient(client: Client)` + +Creates a new `EventClient`. + +**Parameters:** + +- `client` (`Client`): An instance of `Client`. + +--- + +## Methods + +### `getAllEventHandlers(): Promise` + +Gets all event handlers registered in Conductor. + +**Returns:** + +- `Promise`: An array of all event handlers. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get all event handlers +const handlers = await eventClient.getAllEventHandlers(); +console.log(`Found ${handlers.length} event handlers`); +``` + +--- + +### `addEventHandler(eventHandler: EventHandler): Promise` + +Adds a single event handler. + +**Parameters:** + +- `eventHandler` (`EventHandler`): The event handler to add. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Add an event handler that starts a workflow when an event is received +await eventClient.addEventHandler({ + name: "order_created_handler", + event: "order.created", + active: true, + description: "Starts fulfillment workflow when order is created", + actions: [ + { + action: "start_workflow", + start_workflow: { + name: "fulfill_order", + version: 1, + input: { + orderId: "${event.orderId}", + }, + }, + }, + ], +}); +``` + +--- + +### `addEventHandlers(eventHandlers: EventHandler[]): Promise` + +Adds multiple event handlers at once. + +**Parameters:** + +- `eventHandlers` (`EventHandler[]`): An array of event handlers to add. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Add multiple event handlers +await eventClient.addEventHandlers([ + { + name: "order_created_handler", + event: "order.created", + active: true, + actions: [ + { + action: "start_workflow", + start_workflow: { + name: "fulfill_order", + version: 1, + }, + }, + ], + }, + { + name: "order_cancelled_handler", + event: "order.cancelled", + active: true, + actions: [ + { + action: "start_workflow", + start_workflow: { + name: "cancel_order", + version: 1, + }, + }, + ], + }, +]); +``` + +--- + +### `updateEventHandler(eventHandler: EventHandler): Promise` + +Updates an existing event handler. + +**Parameters:** + +- `eventHandler` (`EventHandler`): The updated event handler (must include the `name` field). + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Update an existing handler +await eventClient.updateEventHandler({ + name: "order_created_handler", + event: "order.created", + active: false, // Deactivate the handler + description: "Updated description", + actions: [ + { + action: "start_workflow", + start_workflow: { + name: "fulfill_order_v2", // Updated workflow name + version: 2, + }, + }, + ], +}); +``` + +--- + +### `getEventHandlerByName(eventHandlerName: string): Promise` + +Gets a specific event handler by name. + +**Parameters:** + +- `eventHandlerName` (`string`): The name of the event handler. + +**Returns:** + +- `Promise`: The event handler. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get a specific handler +const handler = await eventClient.getEventHandlerByName("order_created_handler"); +console.log(`Handler is ${handler.active ? "active" : "inactive"}`); +``` + +--- + +### `getEventHandlersForEvent(event: string, activeOnly?: boolean): Promise` + +Gets all event handlers registered for a specific event. + +**Parameters:** + +- `event` (`string`): The event name. +- `activeOnly` (`boolean`, optional): If `true`, only returns active handlers. Defaults to `false`. + +**Returns:** + +- `Promise`: An array of event handlers for the specified event. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get all handlers for an event +const handlers = await eventClient.getEventHandlersForEvent("order.created"); + +// Get only active handlers +const activeHandlers = await eventClient.getEventHandlersForEvent( + "order.created", + true +); +``` + +--- + +### `removeEventHandler(name: string): Promise` + +Removes an event handler by name. + +**Parameters:** + +- `name` (`string`): The name of the event handler to remove. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Remove an event handler +await eventClient.removeEventHandler("order_created_handler"); +``` + +--- + +### `handleIncomingEvent(data: Record): Promise` + +Handles an incoming event. This triggers all active event handlers registered for the event. + +**Parameters:** + +- `data` (`Record`): The event data. Must include an `event` field specifying the event name. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Publish an event +await eventClient.handleIncomingEvent({ + event: "order.created", + orderId: "ORDER-123", + customerId: "CUST-456", + amount: "99.99", + timestamp: Date.now().toString(), +}); +``` + +--- + +### `getTagsForEventHandler(name: string): Promise` + +Gets all tags associated with an event handler. + +**Parameters:** + +- `name` (`string`): The name of the event handler. + +**Returns:** + +- `Promise`: An array of tags. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get tags for a handler +const tags = await eventClient.getTagsForEventHandler("order_created_handler"); +console.log(`Handler has ${tags.length} tags`); +``` + +--- + +### `putTagForEventHandler(name: string, tags: Tag[]): Promise` + +Sets tags for an event handler (replaces existing tags). + +**Parameters:** + +- `name` (`string`): The name of the event handler. +- `tags` (`Tag[]`): An array of tags to set. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Set tags for a handler +await eventClient.putTagForEventHandler("order_created_handler", [ + { key: "environment", value: "production" }, + { key: "team", value: "fulfillment" }, + { key: "priority", value: "high" }, +]); +``` + +--- + +### `deleteTagForEventHandler(name: string, tag: Tag): Promise` + +Deletes a specific tag from an event handler. + +**Parameters:** + +- `name` (`string`): The name of the event handler. +- `tag` (`Tag`): The tag to delete (must match both `key` and `value`). + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Delete a specific tag +await eventClient.deleteTagForEventHandler("order_created_handler", { + key: "priority", + value: "high", +}); +``` + +--- + +### `deleteTagsForEventHandler(name: string, tags: Tag[]): Promise` + +Deletes multiple tags from an event handler. + +**Parameters:** + +- `name` (`string`): The name of the event handler. +- `tags` (`Tag[]`): An array of tags to delete. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Delete multiple tags +await eventClient.deleteTagsForEventHandler("order_created_handler", [ + { key: "priority", value: "high" }, + { key: "team", value: "fulfillment" }, +]); +``` + +--- + +### `getAllActiveEventHandlers(): Promise` + +Gets all active event handlers with execution information (execution view). + +**Returns:** + +- `Promise`: A search result containing active event handlers with execution data. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get all active handlers with execution info +const result = await eventClient.getAllActiveEventHandlers(); +console.log(`Found ${result.totalHits} active handlers`); +result.results?.forEach((handler) => { + console.log(`${handler.name}: ${handler.numberOfActions} actions`); +}); +``` + +--- + +### `getEventExecutions(eventHandlerName: string, from?: number): Promise` + +Gets execution history for a specific event handler. + +**Parameters:** + +- `eventHandlerName` (`string`): The name of the event handler. +- `from` (`number`, optional): Pagination cursor for retrieving more results. + +**Returns:** + +- `Promise`: An array of event executions. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get execution history for a handler +const executions = await eventClient.getEventExecutions("order_created_handler"); +executions.forEach((exec) => { + console.log(`Execution ${exec.id}: ${exec.status}`); +}); +``` + +--- + +### `getEventHandlersWithStats(from?: number): Promise` + +Gets all event handlers with statistics (messages view). + +**Parameters:** + +- `from` (`number`, optional): Pagination cursor for retrieving more results. + +**Returns:** + +- `Promise`: A search result containing event handlers with statistics. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get handlers with statistics +const result = await eventClient.getEventHandlersWithStats(); +result.results?.forEach((handler) => { + console.log( + `${handler.name}: ${handler.numberOfMessages} messages, ${handler.numberOfActions} actions` + ); +}); +``` + +--- + +### `getEventMessages(event: string, from?: number): Promise` + +Gets all messages for a specific event. + +**Parameters:** + +- `event` (`string`): The event name. +- `from` (`number`, optional): Pagination cursor for retrieving more results. + +**Returns:** + +- `Promise`: An array of event messages. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get all messages for an event +const messages = await eventClient.getEventMessages("order.created"); +messages.forEach((msg) => { + console.log(`Message ${msg.id}: ${msg.status}`); + console.log(`Payload:`, msg.fullPayload); +}); +``` + +--- + +### `testConnectivity(input: ConnectivityTestInput): Promise` + +Tests connectivity for a queue using a workflow with an EVENT task and an EventHandler. + +**Parameters:** + +- `input` (`ConnectivityTestInput`): The connectivity test configuration. + +**Returns:** + +- `Promise`: The test result. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Test connectivity +const result = await eventClient.testConnectivity({ + sink: "sqs:my-queue", + input: { + testKey: "testValue", + }, +}); + +console.log(`Test ${result.successful ? "passed" : "failed"}`); +if (!result.successful) { + console.log(`Reason: ${result.reason}`); +} +``` + +--- + +### `test(): Promise` + +Tests the event endpoint (as exposed by API). + +**Returns:** + +- `Promise`: A test event handler response. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Test the endpoint +const result = await eventClient.test(); +console.log("Test endpoint response:", result); +``` + +--- + +### `getAllQueueConfigs(): Promise<{ [key: string]: string }>` + +Gets all queue configurations. + +**Returns:** + +- `Promise<{ [key: string]: string }>`: A record of queue names. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get all queue configs +const queues = await eventClient.getAllQueueConfigs(); +console.log(`Found ${Object.keys(queues).length} queues`); +``` + +--- + +### `getQueueConfig(queueType: string, queueName: string): Promise>` + +Gets the configuration for a specific queue. + +**Parameters:** + +- `queueType` (`string`): The type of queue (e.g., `"sqs"`, `"kafka"`). +- `queueName` (`string`): The name of the queue. + +**Returns:** + +- `Promise>`: The queue configuration. + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Get queue config +const config = await eventClient.getQueueConfig("sqs", "my-queue"); +console.log("Queue config:", config); +``` + +--- + +### `putQueueConfig(queueType: string, queueName: string, config: string): Promise` + +Creates or updates a queue configuration by name. + +**Deprecated:** Prefer server's newer endpoints if available. + +**Parameters:** + +- `queueType` (`string`): The type of queue. +- `queueName` (`string`): The name of the queue. +- `config` (`string`): The queue configuration as a string. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Set queue config +await eventClient.putQueueConfig("sqs", "my-queue", '{"region": "us-east-1"}'); +``` + +--- + +### `deleteQueueConfig(queueType: string, queueName: string): Promise` + +Deletes a queue configuration. + +**Parameters:** + +- `queueType` (`string`): The type of queue. +- `queueName` (`string`): The name of the queue. + +**Returns:** + +- `Promise` + +**Example:** + +```typescript +import { EventClient } from "@io-orkes/conductor-javascript"; + +const eventClient = new EventClient(client); + +// Delete queue config +await eventClient.deleteQueueConfig("sqs", "my-queue"); +``` + +--- + +## Type Definitions + +### `EventHandler` + +```typescript +export type EventHandler = { + actions?: Array; + active?: boolean; + condition?: string; + createdBy?: string; + description?: string; + evaluatorType?: string; + event?: string; + name?: string; + orgId?: string; + tags?: Array; +}; +``` + +### `Action` + +```typescript +export type Action = { + action?: "start_workflow" | "complete_task" | "fail_task" | "terminate_workflow" | "update_workflow_variables"; + complete_task?: TaskDetails; + expandInlineJSON?: boolean; + fail_task?: TaskDetails; + start_workflow?: StartWorkflowRequest; + terminate_workflow?: TerminateWorkflow; + update_workflow_variables?: UpdateWorkflowVariables; +}; +``` + +### `Tag` + +```typescript +export type Tag = { + key?: string; + /** + * @deprecated + */ + type?: string; + value?: string; +}; +``` + +### `ConnectivityTestInput` + +```typescript +export type ConnectivityTestInput = { + input?: { + [key: string]: unknown; + }; + sink: string; +}; +``` + +### `ConnectivityTestResult` + +```typescript +export type ConnectivityTestResult = { + reason?: string; + successful?: boolean; + workflowId?: string; +}; +``` + +### `ExtendedEventExecution` + +```typescript +export type ExtendedEventExecution = { + action?: "start_workflow" | "complete_task" | "fail_task" | "terminate_workflow" | "update_workflow_variables"; + created?: number; + event?: string; + eventHandler?: EventHandler; + fullMessagePayload?: { + [key: string]: unknown; + }; + id?: string; + messageId?: string; + name?: string; + orgId?: string; + output?: { + [key: string]: unknown; + }; + payload?: { + [key: string]: unknown; + }; + status?: "IN_PROGRESS" | "COMPLETED" | "FAILED" | "SKIPPED"; + statusDescription?: string; +}; +``` + +### `EventMessage` + +```typescript +export type EventMessage = { + createdAt?: number; + eventExecutions?: Array; + eventTarget?: string; + eventType?: "WEBHOOK" | "MESSAGE"; + fullPayload?: { + [key: string]: unknown; + }; + id?: string; + orgId?: string; + payload?: string; + status?: "RECEIVED" | "HANDLED" | "REJECTED"; + statusDescription?: string; +}; +``` + +### `SearchResultHandledEventResponse` + +```typescript +export type SearchResultHandledEventResponse = { + results?: Array; + totalHits?: number; +}; +``` + +### `HandledEventResponse` + +```typescript +export type HandledEventResponse = { + active?: boolean; + event?: string; + name?: string; + numberOfActions?: number; + numberOfMessages?: number; +}; +``` + diff --git a/src/common/index.ts b/src/common/index.ts index 16962714..31d89b89 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -5,6 +5,8 @@ export type { Client } from "./open-api/client"; export type { Action, CircuitBreakerTransitionResponse, + ConnectivityTestInput, + ConnectivityTestResult, EventHandler, GenerateTokenRequest, PollData, @@ -15,11 +17,13 @@ export type { ScrollableSearchResultWorkflowSummary, SearchResultTaskSummary, SearchResultWorkflowScheduleExecutionModel, + SearchResultHandledEventResponse, ServiceRegistry, ServiceMethod, SkipTaskRequest, StartWorkflowRequest, SubWorkflowParams, + Tag, Task, TaskDef, TaskDetails, @@ -37,6 +41,8 @@ export type { WorkflowDef, WorkflowRun, ExtendedWorkflowDef, + ExtendedEventExecution, + EventMessage, HumanTaskUser, HumanTaskDefinition, HumanTaskAssignment, diff --git a/src/core/eventClient.ts b/src/core/eventClient.ts index a9d734f6..87ac2c7b 100644 --- a/src/core/eventClient.ts +++ b/src/core/eventClient.ts @@ -5,15 +5,15 @@ import { } from "../common/open-api/sdk.gen"; import { Client } from "../common/open-api/client"; import { handleSdkError } from "./helpers"; -import type { EventHandler } from "../common"; -import { +import type { + EventHandler, ExtendedEventExecution, EventMessage, SearchResultHandledEventResponse, Tag, ConnectivityTestInput, ConnectivityTestResult, -} from "../common/open-api/types.gen"; +} from "../common"; export class EventClient { public readonly _client: Client; From 079fc9aa37ee11cbf3791827e539a5fa234b7326 Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Tue, 4 Nov 2025 18:43:38 +0300 Subject: [PATCH 5/8] make linter happy --- integration-tests/common/EventClient.test.ts | 13 ++++++------- src/core/eventClient.ts | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/integration-tests/common/EventClient.test.ts b/integration-tests/common/EventClient.test.ts index 92dd3e9e..bd35ea7d 100644 --- a/integration-tests/common/EventClient.test.ts +++ b/integration-tests/common/EventClient.test.ts @@ -7,7 +7,6 @@ import type { ConnectivityTestInput, Action, } from "../../src/common/open-api/types.gen"; -import { IntegrationResource } from "../../src/common/open-api/sdk.gen"; describe("EventClient", () => { jest.setTimeout(60000); @@ -81,17 +80,17 @@ describe("EventClient", () => { expect(retrievedHandler.actions).toBeDefined(); expect(Array.isArray(retrievedHandler.actions)).toBe(true); expect(retrievedHandler.actions?.length).toBeGreaterThanOrEqual(1); - const retrievedAction = retrievedHandler.actions![0]; - expect(retrievedAction.action).toEqual("start_workflow"); - expect(retrievedAction.start_workflow).toBeDefined(); - expect(retrievedAction.start_workflow?.name).toEqual(workflowName); - expect(retrievedAction.start_workflow?.version).toEqual(1); + const retrievedAction = retrievedHandler.actions?.[0]; + expect(retrievedAction?.action).toEqual("start_workflow"); + expect(retrievedAction?.start_workflow).toBeDefined(); + expect(retrievedAction?.start_workflow?.name).toEqual(workflowName); + expect(retrievedAction?.start_workflow?.version).toEqual(1); expect(retrievedHandler.tags).toBeDefined(); expect(Array.isArray(retrievedHandler.tags)).toBe(true); expect(retrievedHandler.tags?.length).toBeGreaterThanOrEqual(2); eventHandler.tags?.forEach((tag) => { - const foundTag = retrievedHandler.tags!.find( + const foundTag = retrievedHandler.tags?.find( (t) => t.key === tag.key && t.value === tag.value ); expect(foundTag).toBeDefined(); diff --git a/src/core/eventClient.ts b/src/core/eventClient.ts index 87ac2c7b..33cb3906 100644 --- a/src/core/eventClient.ts +++ b/src/core/eventClient.ts @@ -147,7 +147,7 @@ export class EventClient { * @returns {Promise>} * @throws {ConductorSdkError} */ - public async getAllQueueConfigs(): Promise<{ [key: string]: string }> { + public async getAllQueueConfigs(): Promise> { try { const { data } = await EventResource.getQueueNames({ client: this._client, From 4b0e388884da90c1b70df6175d7d08b8580eee71 Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Tue, 4 Nov 2025 18:44:46 +0300 Subject: [PATCH 6/8] Update index.ts --- src/core/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/index.ts b/src/core/index.ts index 1a35d3bd..959879f3 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -7,4 +7,4 @@ export * from "./schedulerClient"; export * from "./taskClient"; export * from "./templateClient"; export * from "./metadataClient"; -export * from "./EventClient"; \ No newline at end of file +export * from "./eventClient"; \ No newline at end of file From dc3bbd2672bbf1b51f0158d8dec15c684db239f2 Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Tue, 4 Nov 2025 19:08:09 +0300 Subject: [PATCH 7/8] separate v4 and v5 tests --- integration-tests/common/EventClient.test.ts | 279 ---------------- integration-tests/v5-only/EventClient.test.ts | 308 ++++++++++++++++++ 2 files changed, 308 insertions(+), 279 deletions(-) create mode 100644 integration-tests/v5-only/EventClient.test.ts diff --git a/integration-tests/common/EventClient.test.ts b/integration-tests/common/EventClient.test.ts index bd35ea7d..91d1a6f4 100644 --- a/integration-tests/common/EventClient.test.ts +++ b/integration-tests/common/EventClient.test.ts @@ -382,77 +382,6 @@ describe("EventClient", () => { }); }); - describe("Event Processing", () => { - test("Should handle incoming event", async () => { - const eventClient = new EventClient(await orkesConductorClient()); - const handlerName = createUniqueName("event-handler"); - const eventName = createUniqueName("event"); - - const workflowName = createUniqueName("test-workflow"); - const startWorkflowAction: Action = { - action: "start_workflow", - start_workflow: { - name: workflowName, - version: 1, - input: {}, - }, - }; - - const eventHandler: EventHandler = { - name: handlerName, - event: eventName, - active: true, - actions: [startWorkflowAction], - description: `Test handler for ${eventName}`, - }; - await eventClient.addEventHandler(eventHandler); - - const retrievedHandler = await eventClient.getEventHandlerByName( - handlerName - ); - expect(retrievedHandler.name).toEqual(handlerName); - expect(retrievedHandler.event).toEqual(eventName); - expect(retrievedHandler.active).toBe(true); - - const handlersForEvent = await eventClient.getEventHandlersForEvent( - eventName - ); - expect(handlersForEvent.length).toBeGreaterThan(0); - expect(handlersForEvent.some((h) => h.name === handlerName)).toBe(true); - - const eventData: Record = { - event: eventName, - source: "integration-test", - timestamp: Date.now().toString(), - message: "test event", - }; - - await eventClient.handleIncomingEvent(eventData); - - const handlerAfterEvent = await eventClient.getEventHandlerByName( - handlerName - ); - expect(handlerAfterEvent.active).toBe(true); - expect(handlerAfterEvent.event).toEqual(eventName); - - const executions = await eventClient.getEventExecutions(handlerName); - expect(Array.isArray(executions)).toBe(true); - - const ourExecution = executions.find( - (exec) => exec.event === eventName || exec.name === handlerName - ); - expect(ourExecution).toBeDefined(); - if (ourExecution?.event) { - expect(ourExecution.event).toEqual(eventName); - } - if (ourExecution?.name) { - expect(ourExecution.name).toEqual(handlerName); - } - - await eventClient.removeEventHandler(handlerName); - }); - }); - describe("Test Endpoint", () => { test("Should call test endpoint", async () => { const eventClient = new EventClient(await orkesConductorClient()); @@ -462,214 +391,6 @@ describe("EventClient", () => { }); }); - describe("Event Executions and Statistics", () => { - test("Should get all active event handlers (execution view)", async () => { - const eventClient = new EventClient(await orkesConductorClient()); - - const handlerName = createUniqueName("event-handler"); - const eventName = createUniqueName("event"); - const workflowName = createUniqueName("test-workflow"); - - const startWorkflowAction: Action = { - action: "start_workflow", - start_workflow: { - name: workflowName, - version: 1, - input: {}, - }, - }; - - const eventHandler: EventHandler = { - name: handlerName, - event: eventName, - active: true, - actions: [startWorkflowAction], - description: `Test handler for ${eventName}`, - }; - - await eventClient.addEventHandler(eventHandler); - - // Trigger an event so the handler processes it and appears in execution view - const eventData: Record = { - event: eventName, - source: "integration-test", - timestamp: Date.now().toString(), - message: "test event", - }; - await eventClient.handleIncomingEvent(eventData); - - // Get all active event handlers (execution view only shows handlers with executions) - const result = await eventClient.getAllActiveEventHandlers(); - - expect(result).toBeDefined(); - expect(result).toHaveProperty("results"); - expect(Array.isArray(result.results)).toBe(true); - expect(result.results?.length).toBeGreaterThan(0); - - // Verify our handler appears in the results - const foundHandler = result.results?.find( - (h) => h.name === handlerName && h.event === eventName - ); - expect(foundHandler).toBeDefined(); - expect(foundHandler?.name).toBe(handlerName); - expect(foundHandler?.event).toBe(eventName); - expect(foundHandler?.active).toBe(true); - - await eventClient.removeEventHandler(handlerName); - }); - - test("Should get event executions for a handler", async () => { - const eventClient = new EventClient(await orkesConductorClient()); - const handlerName = createUniqueName("event-handler"); - const eventName = createUniqueName("event"); - const workflowName = createUniqueName("test-workflow"); - - const startWorkflowAction: Action = { - action: "start_workflow", - start_workflow: { - name: workflowName, - version: 1, - input: {}, - }, - }; - - const eventHandler: EventHandler = { - name: handlerName, - event: eventName, - active: true, - actions: [startWorkflowAction], - description: `Test handler for ${eventName}`, - }; - await eventClient.addEventHandler(eventHandler); - - // Trigger an event so the handler processes it and creates an execution - const eventData = { - event: eventName, - source: "integration-test", - timestamp: Date.now().toString(), - message: "test event", - }; - await eventClient.handleIncomingEvent(eventData); - - const executions = await eventClient.getEventExecutions(handlerName, 0); - expect(Array.isArray(executions)).toBe(true); - expect(executions.length).toBeGreaterThan(0); - - // Find our execution in the results - const ourExecution = executions.find( - (exec) => exec.name === handlerName && exec.event === eventName - ); - expect(ourExecution).toBeDefined(); - expect(ourExecution?.name).toBe(handlerName); - expect(ourExecution?.event).toBe(eventName); - expect(typeof ourExecution?.id).toBe("string"); - expect(typeof ourExecution?.created).toBe("number"); - expect(ourExecution?.action).toBe("start_workflow"); - - await eventClient.removeEventHandler(handlerName); - }); - - test("Should get event handlers with statistics", async () => { - const eventClient = new EventClient(await orkesConductorClient()); - const handlerName = createUniqueName("event-handler"); - const eventName = createUniqueName("event"); - const workflowName = createUniqueName("test-workflow"); - - const startWorkflowAction: Action = { - action: "start_workflow", - start_workflow: { - name: workflowName, - version: 1, - input: {}, - }, - }; - - const eventHandler: EventHandler = { - name: handlerName, - event: eventName, - active: true, - actions: [startWorkflowAction], - description: `Test handler for ${eventName}`, - }; - await eventClient.addEventHandler(eventHandler); - - // Trigger an event so the handler processes it and creates an execution - const eventData = { - event: eventName, - source: "integration-test", - timestamp: Date.now().toString(), - message: "test event", - }; - await eventClient.handleIncomingEvent(eventData); - - const response = await eventClient.getEventHandlersWithStats(0); - expect(response).toBeDefined(); - expect(response).toHaveProperty("results"); - expect(Array.isArray(response.results)).toBe(true); - expect(response.results?.length).toBeGreaterThan(0); - - // Find our handler in the results - const foundHandler = response.results?.find((h) => h.event === eventName); - expect(foundHandler).toBeDefined(); - expect(foundHandler?.event).toBe(eventName); - expect(foundHandler?.active).toBe(true); - expect(typeof foundHandler?.numberOfActions).toBe("number"); - expect(typeof foundHandler?.numberOfMessages).toBe("number"); - - await eventClient.removeEventHandler(handlerName); - }); - - test("Should get event messages for an event", async () => { - const eventClient = new EventClient(await orkesConductorClient()); - const handlerName = createUniqueName("event-handler"); - const eventName = createUniqueName("event"); - const workflowName = createUniqueName("test-workflow"); - - const startWorkflowAction: Action = { - action: "start_workflow", - start_workflow: { - name: workflowName, - version: 1, - input: {}, - }, - }; - - const eventHandler: EventHandler = { - name: handlerName, - event: eventName, - active: true, - actions: [startWorkflowAction], - description: `Test handler for ${eventName}`, - }; - await eventClient.addEventHandler(eventHandler); - - // Trigger an event to create messages - const eventData = { - event: eventName, - source: "integration-test", - timestamp: Date.now().toString(), - message: "test event", - }; - await eventClient.handleIncomingEvent(eventData); - - const messages = await eventClient.getEventMessages(eventName); - expect(Array.isArray(messages)).toBe(true); - expect(messages.length).toBeGreaterThan(0); - - // Find our message in the results - const ourMessage = messages.find((msg) => msg.eventTarget === eventName); - expect(ourMessage).toBeDefined(); - expect(typeof ourMessage).toBe("object"); - expect(typeof ourMessage?.id).toBe("string"); - expect(typeof ourMessage?.createdAt).toBe("number"); - expect(ourMessage?.fullPayload?.event).toBe(eventName); - expect(ourMessage?.fullPayload?.source).toBe("integration-test"); - expect(ourMessage?.fullPayload?.message).toBe("test event"); - - await eventClient.removeEventHandler(handlerName); - }); - }); - describe("Error Handling", () => { test("Should throw error when getting non-existent event handler", async () => { const eventClient = new EventClient(await orkesConductorClient()); diff --git a/integration-tests/v5-only/EventClient.test.ts b/integration-tests/v5-only/EventClient.test.ts new file mode 100644 index 00000000..6fd6b0d9 --- /dev/null +++ b/integration-tests/v5-only/EventClient.test.ts @@ -0,0 +1,308 @@ +import { expect, describe, test, jest } from "@jest/globals"; +import { orkesConductorClient } from "../../src/orkes"; +import { EventClient } from "../../src/core"; +import type { EventHandler } from "../../src/common"; +import type { + Tag, + ConnectivityTestInput, + Action, +} from "../../src/common/open-api/types.gen"; + +describe("EventClient", () => { + jest.setTimeout(60000); + // Helper function to create unique names + const createUniqueName = (prefix: string) => + `jsSdkTest:${prefix}:${Date.now()}`; + + // Helper function to create a test event handler + const createEventHandler = ( + name: string, + event: string, + active = true + ): EventHandler => ({ + name, + event, + active, + actions: [], + description: `Test event handler: ${name}`, + }); + + describe("Event Processing", () => { + test("Should handle incoming event", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + + const workflowName = createUniqueName("test-workflow"); + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + const retrievedHandler = await eventClient.getEventHandlerByName( + handlerName + ); + expect(retrievedHandler.name).toEqual(handlerName); + expect(retrievedHandler.event).toEqual(eventName); + expect(retrievedHandler.active).toBe(true); + + const handlersForEvent = await eventClient.getEventHandlersForEvent( + eventName + ); + expect(handlersForEvent.length).toBeGreaterThan(0); + expect(handlersForEvent.some((h) => h.name === handlerName)).toBe(true); + + const eventData: Record = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + + await eventClient.handleIncomingEvent(eventData); + + const handlerAfterEvent = await eventClient.getEventHandlerByName( + handlerName + ); + expect(handlerAfterEvent.active).toBe(true); + expect(handlerAfterEvent.event).toEqual(eventName); + + const executions = await eventClient.getEventExecutions(handlerName); + expect(Array.isArray(executions)).toBe(true); + + const ourExecution = executions.find( + (exec) => exec.event === eventName || exec.name === handlerName + ); + expect(ourExecution).toBeDefined(); + if (ourExecution?.event) { + expect(ourExecution.event).toEqual(eventName); + } + if (ourExecution?.name) { + expect(ourExecution.name).toEqual(handlerName); + } + + await eventClient.removeEventHandler(handlerName); + }); + }); + + describe("Event Executions and Statistics", () => { + test("Should get all active event handlers (execution view)", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + + await eventClient.addEventHandler(eventHandler); + + // Trigger an event so the handler processes it and appears in execution view + const eventData: Record = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + // Get all active event handlers (execution view only shows handlers with executions) + const result = await eventClient.getAllActiveEventHandlers(); + + expect(result).toBeDefined(); + expect(result).toHaveProperty("results"); + expect(Array.isArray(result.results)).toBe(true); + expect(result.results?.length).toBeGreaterThan(0); + + // Verify our handler appears in the results + const foundHandler = result.results?.find( + (h) => h.name === handlerName && h.event === eventName + ); + expect(foundHandler).toBeDefined(); + expect(foundHandler?.name).toBe(handlerName); + expect(foundHandler?.event).toBe(eventName); + expect(foundHandler?.active).toBe(true); + + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get event executions for a handler", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + // Trigger an event so the handler processes it and creates an execution + const eventData = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + const executions = await eventClient.getEventExecutions(handlerName, 0); + expect(Array.isArray(executions)).toBe(true); + expect(executions.length).toBeGreaterThan(0); + + // Find our execution in the results + const ourExecution = executions.find( + (exec) => exec.name === handlerName && exec.event === eventName + ); + expect(ourExecution).toBeDefined(); + expect(ourExecution?.name).toBe(handlerName); + expect(ourExecution?.event).toBe(eventName); + expect(typeof ourExecution?.id).toBe("string"); + expect(typeof ourExecution?.created).toBe("number"); + expect(ourExecution?.action).toBe("start_workflow"); + + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get event handlers with statistics", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + // Trigger an event so the handler processes it and creates an execution + const eventData = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + const response = await eventClient.getEventHandlersWithStats(0); + expect(response).toBeDefined(); + expect(response).toHaveProperty("results"); + expect(Array.isArray(response.results)).toBe(true); + expect(response.results?.length).toBeGreaterThan(0); + + // Find our handler in the results + const foundHandler = response.results?.find((h) => h.event === eventName); + expect(foundHandler).toBeDefined(); + expect(foundHandler?.event).toBe(eventName); + expect(foundHandler?.active).toBe(true); + expect(typeof foundHandler?.numberOfActions).toBe("number"); + expect(typeof foundHandler?.numberOfMessages).toBe("number"); + + await eventClient.removeEventHandler(handlerName); + }); + + test("Should get event messages for an event", async () => { + const eventClient = new EventClient(await orkesConductorClient()); + const handlerName = createUniqueName("event-handler"); + const eventName = createUniqueName("event"); + const workflowName = createUniqueName("test-workflow"); + + const startWorkflowAction: Action = { + action: "start_workflow", + start_workflow: { + name: workflowName, + version: 1, + input: {}, + }, + }; + + const eventHandler: EventHandler = { + name: handlerName, + event: eventName, + active: true, + actions: [startWorkflowAction], + description: `Test handler for ${eventName}`, + }; + await eventClient.addEventHandler(eventHandler); + + // Trigger an event to create messages + const eventData = { + event: eventName, + source: "integration-test", + timestamp: Date.now().toString(), + message: "test event", + }; + await eventClient.handleIncomingEvent(eventData); + + const messages = await eventClient.getEventMessages(eventName); + expect(Array.isArray(messages)).toBe(true); + expect(messages.length).toBeGreaterThan(0); + + // Find our message in the results + const ourMessage = messages.find((msg) => msg.eventTarget === eventName); + expect(ourMessage).toBeDefined(); + expect(typeof ourMessage).toBe("object"); + expect(typeof ourMessage?.id).toBe("string"); + expect(typeof ourMessage?.createdAt).toBe("number"); + expect(ourMessage?.fullPayload?.event).toBe(eventName); + expect(ourMessage?.fullPayload?.source).toBe("integration-test"); + expect(ourMessage?.fullPayload?.message).toBe("test event"); + + await eventClient.removeEventHandler(handlerName); + }); + }); +}); From e80440859cde220ca1fd5a82d69378e110668f8e Mon Sep 17 00:00:00 2001 From: Dmitry Borisov Date: Tue, 4 Nov 2025 19:09:18 +0300 Subject: [PATCH 8/8] make linter happy --- integration-tests/v5-only/EventClient.test.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/integration-tests/v5-only/EventClient.test.ts b/integration-tests/v5-only/EventClient.test.ts index 6fd6b0d9..22455e1a 100644 --- a/integration-tests/v5-only/EventClient.test.ts +++ b/integration-tests/v5-only/EventClient.test.ts @@ -2,11 +2,7 @@ import { expect, describe, test, jest } from "@jest/globals"; import { orkesConductorClient } from "../../src/orkes"; import { EventClient } from "../../src/core"; import type { EventHandler } from "../../src/common"; -import type { - Tag, - ConnectivityTestInput, - Action, -} from "../../src/common/open-api/types.gen"; +import type { Action } from "../../src/common/open-api/types.gen"; describe("EventClient", () => { jest.setTimeout(60000); @@ -14,19 +10,6 @@ describe("EventClient", () => { const createUniqueName = (prefix: string) => `jsSdkTest:${prefix}:${Date.now()}`; - // Helper function to create a test event handler - const createEventHandler = ( - name: string, - event: string, - active = true - ): EventHandler => ({ - name, - event, - active, - actions: [], - description: `Test event handler: ${name}`, - }); - describe("Event Processing", () => { test("Should handle incoming event", async () => { const eventClient = new EventClient(await orkesConductorClient());