arvo-event-handler 2.3.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AbstractArvoEventHandler/index.d.ts +1 -1
- package/dist/ArvoEventHandler/helpers.d.ts +40 -6
- package/dist/ArvoEventHandler/helpers.js +40 -6
- package/dist/ArvoEventHandler/index.d.ts +78 -49
- package/dist/ArvoEventHandler/index.js +151 -81
- package/dist/ArvoEventHandler/types.d.ts +25 -2
- package/dist/ArvoMachine/createMachine.d.ts +208 -0
- package/dist/ArvoMachine/createMachine.js +283 -0
- package/dist/ArvoMachine/index.d.ts +93 -0
- package/dist/ArvoMachine/index.js +160 -0
- package/dist/ArvoMachine/types.d.ts +194 -0
- package/dist/ArvoMachine/utils.d.ts +40 -0
- package/dist/ArvoMachine/utils.js +70 -0
- package/dist/ArvoOrchestrator/error.d.ts +16 -0
- package/dist/ArvoOrchestrator/error.js +43 -0
- package/dist/ArvoOrchestrator/factory.d.ts +28 -0
- package/dist/ArvoOrchestrator/factory.js +56 -0
- package/dist/ArvoOrchestrator/index.d.ts +69 -0
- package/dist/ArvoOrchestrator/index.js +597 -0
- package/dist/ArvoOrchestrator/types.d.ts +98 -0
- package/dist/ArvoResumable/factory.d.ts +50 -0
- package/dist/ArvoResumable/factory.js +70 -0
- package/dist/ArvoResumable/index.d.ts +141 -0
- package/dist/ArvoResumable/index.js +694 -0
- package/dist/ArvoResumable/types.d.ts +147 -0
- package/dist/ArvoResumable/types.js +2 -0
- package/dist/MachineExecutionEngine/index.d.ts +29 -0
- package/dist/MachineExecutionEngine/index.js +132 -0
- package/dist/MachineExecutionEngine/interface.d.ts +14 -0
- package/dist/MachineExecutionEngine/interface.js +2 -0
- package/dist/MachineExecutionEngine/types.d.ts +14 -0
- package/dist/MachineExecutionEngine/types.js +2 -0
- package/dist/MachineMemory/Simple.d.ts +51 -0
- package/dist/MachineMemory/Simple.js +158 -0
- package/dist/MachineMemory/TelemetredSimple.d.ts +51 -0
- package/dist/MachineMemory/TelemetredSimple.js +230 -0
- package/dist/MachineMemory/interface.d.ts +57 -0
- package/dist/MachineMemory/interface.js +2 -0
- package/dist/MachineMemory/utils.d.ts +1 -0
- package/dist/MachineMemory/utils.js +18 -0
- package/dist/MachineRegistry/index.d.ts +37 -0
- package/dist/MachineRegistry/index.js +87 -0
- package/dist/MachineRegistry/interface.d.ts +21 -0
- package/dist/MachineRegistry/interface.js +2 -0
- package/dist/SyncEventResource/index.d.ts +110 -0
- package/dist/SyncEventResource/index.js +280 -0
- package/dist/SyncEventResource/types.d.ts +2 -0
- package/dist/SyncEventResource/types.js +2 -0
- package/dist/index.d.ts +26 -8
- package/dist/index.js +39 -16
- package/dist/utils/SimpleEventBroker/helper.d.ts +166 -0
- package/dist/utils/SimpleEventBroker/helper.js +276 -0
- package/dist/utils/SimpleEventBroker/index.d.ts +96 -0
- package/dist/utils/SimpleEventBroker/index.js +259 -0
- package/dist/utils/SimpleEventBroker/types.d.ts +6 -0
- package/dist/utils/SimpleEventBroker/types.js +2 -0
- package/dist/utils/SimpleEventBroker/utils.d.ts +1 -0
- package/dist/utils/SimpleEventBroker/utils.js +10 -0
- package/dist/{utils.d.ts → utils/index.d.ts} +3 -36
- package/dist/utils/index.js +91 -0
- package/dist/utils/object/index.d.ts +37 -0
- package/dist/utils/object/index.js +63 -0
- package/package.json +6 -12
- package/dist/ArvoEventRouter/helpers.d.ts +0 -19
- package/dist/ArvoEventRouter/helpers.js +0 -22
- package/dist/ArvoEventRouter/index.d.ts +0 -89
- package/dist/ArvoEventRouter/index.js +0 -267
- package/dist/ArvoEventRouter/types.d.ts +0 -36
- package/dist/ArvoEventRouter/utils.d.ts +0 -29
- package/dist/ArvoEventRouter/utils.js +0 -42
- package/dist/MultiArvoEventHandler/helpers.d.ts +0 -48
- package/dist/MultiArvoEventHandler/helpers.js +0 -56
- package/dist/MultiArvoEventHandler/index.d.ts +0 -68
- package/dist/MultiArvoEventHandler/index.js +0 -205
- package/dist/MultiArvoEventHandler/types.d.ts +0 -64
- package/dist/utils.js +0 -190
- /package/dist/{ArvoEventRouter → ArvoMachine}/types.js +0 -0
- /package/dist/{MultiArvoEventHandler → ArvoOrchestrator}/types.js +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createSimpleEventBroker = void 0;
|
|
40
|
+
var _1 = require(".");
|
|
41
|
+
/**
|
|
42
|
+
* Creates a local event broker configured with domain event handlers and provides event resolution capabilities
|
|
43
|
+
*
|
|
44
|
+
* This factory function establishes a comprehensive event-driven architecture within a single process,
|
|
45
|
+
* automatically wiring event handlers to their source topics and providing sophisticated event propagation
|
|
46
|
+
* with domain-specific routing capabilities. The broker implements sequential queue-based processing
|
|
47
|
+
* with built-in error handling and observability features.
|
|
48
|
+
*
|
|
49
|
+
* **Core Architecture:**
|
|
50
|
+
* The broker acts as an in-memory event bus that connects ArvoResumable orchestrators, ArvoOrchestrator
|
|
51
|
+
* state machines, and ArvoEventHandler services in a unified event-driven system. This enables
|
|
52
|
+
* local testing of distributed workflows and provides a foundation for event-driven microservices.
|
|
53
|
+
*
|
|
54
|
+
* **Event Processing Flow:**
|
|
55
|
+
* 1. Events are published to handler source topics
|
|
56
|
+
* 2. Handlers execute and produce response events
|
|
57
|
+
* 3. Domain-specific events are routed through onDomainedEvents callback
|
|
58
|
+
* 4. Default domain events are automatically propagated through the broker
|
|
59
|
+
* 5. Event chains continue until all handlers complete processing
|
|
60
|
+
*
|
|
61
|
+
* @param eventHandlers - Array of event handlers to register with the broker. Each handler is automatically
|
|
62
|
+
* subscribed to its source topic and executed when matching events are received.
|
|
63
|
+
* Supports ArvoResumable, ArvoOrchestrator, and ArvoEventHandler instances.
|
|
64
|
+
*
|
|
65
|
+
* @param options - Optional configuration for customizing broker behavior and event processing
|
|
66
|
+
* @param options.onError - Custom error handler invoked when processing failures occur. Receives the error
|
|
67
|
+
* and triggering event for logging, monitoring, or recovery actions. Defaults to
|
|
68
|
+
* console.error with structured event information for debugging.
|
|
69
|
+
* @param options.onDomainedEvents - Callback for processing domain-specific events produced by handlers.
|
|
70
|
+
* Enables custom routing logic, external system integration, or
|
|
71
|
+
* domain-specific event processing patterns. Receives events grouped
|
|
72
|
+
* by domain (excluding 'all') and the broker instance for republishing.
|
|
73
|
+
*
|
|
74
|
+
* @returns Configuration object containing the broker instance and event resolution function
|
|
75
|
+
* @returns result.broker - Configured SimpleEventBroker with all handlers subscribed and ready for processing
|
|
76
|
+
* @returns result.resolve - Async function that executes complete event processing chains and returns
|
|
77
|
+
* the final resolved event. Returns null if resolution fails or handler is not found
|
|
78
|
+
* for an intermetiate event.
|
|
79
|
+
*
|
|
80
|
+
* @throws {Error} When event source conflicts with registered handler sources during resolution
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* **Basic Event-Driven Architecture Setup:**
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const userHandler = createArvoEventHandler({
|
|
86
|
+
* contract: userContract,
|
|
87
|
+
* handler: { '1.0.0': async ({ event }) => ({ type: 'user.processed', data: event.data }) }
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* const orderOrchestrator = createArvoResumable({
|
|
91
|
+
* contracts: { self: orderContract, services: { user: userContract } },
|
|
92
|
+
* handler: {
|
|
93
|
+
* '1.0.0': async ({ init, service }) => {
|
|
94
|
+
* if (init) return { services: [{ type: 'user.process', data: init.data }] };
|
|
95
|
+
* if (service) return { complete: { data: { orderId: 'order-123' } } };
|
|
96
|
+
* }
|
|
97
|
+
* }
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* const { broker, resolve } = createSimpleEventBroker([userHandler, orderOrchestrator]);
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* **Advanced Configuration with Domain Routing:**
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const { broker, resolve } = createSimpleEventBroker(
|
|
107
|
+
* [orchestrator, paymentHandler, notificationHandler],
|
|
108
|
+
* {
|
|
109
|
+
* onError: (error, event) => {
|
|
110
|
+
* logger.error('Event processing failed', {
|
|
111
|
+
* error: error.message,
|
|
112
|
+
* eventType: event.type,
|
|
113
|
+
* eventId: event.id,
|
|
114
|
+
* source: event.source,
|
|
115
|
+
* timestamp: new Date().toISOString()
|
|
116
|
+
* });
|
|
117
|
+
* // Could implement retry logic, dead letter queues, etc.
|
|
118
|
+
* },
|
|
119
|
+
* onDomainedEvents: ({ events, broker }) => {
|
|
120
|
+
* // Route payment events to external payment processor
|
|
121
|
+
* if (events.payment) {
|
|
122
|
+
* events.payment.forEach(event => paymentGateway.send(event));
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* // Route notification events to messaging service
|
|
126
|
+
* if (events.notifications) {
|
|
127
|
+
* events.notifications.forEach(event => messagingService.send(event));
|
|
128
|
+
* }
|
|
129
|
+
*
|
|
130
|
+
* // Republish other domain events through the broker
|
|
131
|
+
* Object.entries(events).forEach(([domain, domainEvents]) => {
|
|
132
|
+
* if (!['payment', 'notifications'].includes(domain)) {
|
|
133
|
+
* domainEvents.forEach(event => broker.publish(event));
|
|
134
|
+
* }
|
|
135
|
+
* });
|
|
136
|
+
* }
|
|
137
|
+
* }
|
|
138
|
+
* );
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* **Event Resolution for Integration Testing:**
|
|
143
|
+
* ```typescript
|
|
144
|
+
* // Test complete workflow execution
|
|
145
|
+
* const testEvent = createArvoEvent({
|
|
146
|
+
* type: 'order.create',
|
|
147
|
+
* source: 'test.client',
|
|
148
|
+
* to: 'order.orchestrator',
|
|
149
|
+
* data: { userId: '123', items: ['item1', 'item2'] }
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* const finalEvent = await resolve(testEvent);
|
|
153
|
+
*
|
|
154
|
+
* if (finalEvent) {
|
|
155
|
+
* // Verify the complete workflow executed successfully
|
|
156
|
+
* expect(finalEvent.type).toBe('order.completed');
|
|
157
|
+
* expect(finalEvent.data.orderId).toBeDefined();
|
|
158
|
+
* expect(finalEvent.source).toBe('test.client'); // Original source preserved
|
|
159
|
+
* } else {
|
|
160
|
+
* throw new Error('Order processing workflow failed');
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* **Direct Event Publishing:**
|
|
166
|
+
* ```typescript
|
|
167
|
+
* // Publish events directly to the broker for real-time processing
|
|
168
|
+
* await broker.publish(createArvoEvent({
|
|
169
|
+
* type: 'user.signup',
|
|
170
|
+
* source: 'web.app',
|
|
171
|
+
* to: 'user.service',
|
|
172
|
+
* data: { email: 'user@example.com', name: 'John Doe' }
|
|
173
|
+
* }));
|
|
174
|
+
*
|
|
175
|
+
* // The event will be routed to the user service handler automatically
|
|
176
|
+
* // Any resulting events will propagate through the broker
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @remarks
|
|
180
|
+
* **Event Source Conflict Prevention:**
|
|
181
|
+
* The resolve function validates that the input event's source doesn't conflict
|
|
182
|
+
* with registered handler sources to prevent infinite loops and routing ambiguity.
|
|
183
|
+
*
|
|
184
|
+
* **Sequential Processing Guarantee:**
|
|
185
|
+
* Events are processed sequentially within each topic to maintain ordering
|
|
186
|
+
* guarantees and prevent race conditions in workflow state management.
|
|
187
|
+
*
|
|
188
|
+
* **Integration Testing Benefits:**
|
|
189
|
+
* This pattern enables comprehensive integration testing of event-driven workflows
|
|
190
|
+
* without requiring external message brokers, making test suites faster and
|
|
191
|
+
* more reliable while maintaining production-like behavior patterns.
|
|
192
|
+
*/
|
|
193
|
+
var createSimpleEventBroker = function (eventHandlers, options) {
|
|
194
|
+
var _a;
|
|
195
|
+
var resolvedHandlerName = 'broker.arvo.simple.handle';
|
|
196
|
+
var resolvedHandlerNameDuplicated = false;
|
|
197
|
+
var broker = new _1.SimpleEventBroker({
|
|
198
|
+
maxQueueSize: 1000,
|
|
199
|
+
errorHandler: (_a = options === null || options === void 0 ? void 0 : options.onError) !== null && _a !== void 0 ? _a : (function (error, event) {
|
|
200
|
+
console.error('Broker error:', {
|
|
201
|
+
message: error.message,
|
|
202
|
+
eventType: event.to,
|
|
203
|
+
event: event,
|
|
204
|
+
});
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
// Wire up each handler to its source topic
|
|
208
|
+
// biome-ignore lint/complexity/noForEach: TODO - fix later
|
|
209
|
+
eventHandlers.forEach(function (handler) {
|
|
210
|
+
resolvedHandlerNameDuplicated = broker.topics.includes(resolvedHandlerName);
|
|
211
|
+
broker.subscribe(handler.source, function (event) { return __awaiter(void 0, void 0, void 0, function () {
|
|
212
|
+
var response, _i, _a, evt;
|
|
213
|
+
var _b;
|
|
214
|
+
return __generator(this, function (_c) {
|
|
215
|
+
switch (_c.label) {
|
|
216
|
+
case 0: return [4 /*yield*/, handler.execute(event, {
|
|
217
|
+
inheritFrom: 'EVENT',
|
|
218
|
+
})];
|
|
219
|
+
case 1:
|
|
220
|
+
response = _c.sent();
|
|
221
|
+
_i = 0, _a = response.events;
|
|
222
|
+
_c.label = 2;
|
|
223
|
+
case 2:
|
|
224
|
+
if (!(_i < _a.length)) return [3 /*break*/, 7];
|
|
225
|
+
evt = _a[_i];
|
|
226
|
+
if (!event.domain) return [3 /*break*/, 4];
|
|
227
|
+
return [4 /*yield*/, ((_b = options === null || options === void 0 ? void 0 : options.onDomainedEvents) === null || _b === void 0 ? void 0 : _b.call(options, {
|
|
228
|
+
domain: event.domain,
|
|
229
|
+
event: event,
|
|
230
|
+
broker: broker,
|
|
231
|
+
}))];
|
|
232
|
+
case 3:
|
|
233
|
+
_c.sent();
|
|
234
|
+
return [3 /*break*/, 6];
|
|
235
|
+
case 4: return [4 /*yield*/, broker.publish(evt)];
|
|
236
|
+
case 5:
|
|
237
|
+
_c.sent();
|
|
238
|
+
_c.label = 6;
|
|
239
|
+
case 6:
|
|
240
|
+
_i++;
|
|
241
|
+
return [3 /*break*/, 2];
|
|
242
|
+
case 7: return [2 /*return*/];
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}); }, true);
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
broker: broker,
|
|
249
|
+
resolve: function (_event) { return __awaiter(void 0, void 0, void 0, function () {
|
|
250
|
+
var resolvedEvent;
|
|
251
|
+
return __generator(this, function (_a) {
|
|
252
|
+
switch (_a.label) {
|
|
253
|
+
case 0:
|
|
254
|
+
if (broker.topics.includes(_event.source)) {
|
|
255
|
+
throw new Error("The event source cannot be one of the handlers in the broker. Please update the event.source, the given is '".concat(_event.source, "'"));
|
|
256
|
+
}
|
|
257
|
+
resolvedEvent = null;
|
|
258
|
+
broker.subscribe(_event.source, function (event) { return __awaiter(void 0, void 0, void 0, function () {
|
|
259
|
+
return __generator(this, function (_a) {
|
|
260
|
+
resolvedEvent = event;
|
|
261
|
+
return [2 /*return*/];
|
|
262
|
+
});
|
|
263
|
+
}); });
|
|
264
|
+
return [4 /*yield*/, broker.publish(_event)];
|
|
265
|
+
case 1:
|
|
266
|
+
_a.sent();
|
|
267
|
+
if (resolvedEvent === null) {
|
|
268
|
+
return [2 /*return*/, null];
|
|
269
|
+
}
|
|
270
|
+
return [2 /*return*/, resolvedEvent];
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}); },
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
exports.createSimpleEventBroker = createSimpleEventBroker;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ArvoEvent } from 'arvo-core';
|
|
2
|
+
import type { EventBusListener, EventBusOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A simple event broker for handling local event-driven workflows within function scope.
|
|
5
|
+
* Ideal for composing function handlers and coordinating local business logic steps.
|
|
6
|
+
*
|
|
7
|
+
* @description
|
|
8
|
+
* Use SimpleEventBroker when you need:
|
|
9
|
+
* - Local event handling within a function's execution scope
|
|
10
|
+
* - Decoupling steps in a business process
|
|
11
|
+
* - Simple composition of handlers for a workflow
|
|
12
|
+
* - In-memory event management for a single operation
|
|
13
|
+
*
|
|
14
|
+
* Not suitable for:
|
|
15
|
+
* - Long-running processes or persistent event storage
|
|
16
|
+
* - Cross-process or distributed event handling
|
|
17
|
+
* - High-throughput event processing (>1000s events/sec)
|
|
18
|
+
* - Mission critical or fault-tolerant systems
|
|
19
|
+
* - Complex event routing or filtering
|
|
20
|
+
*
|
|
21
|
+
* Typical use cases:
|
|
22
|
+
* - Coordinating steps in a registration process
|
|
23
|
+
* - Managing validation and processing workflows
|
|
24
|
+
* - Decoupling business logic steps
|
|
25
|
+
* - Local event-driven state management
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // During a registration flow
|
|
30
|
+
* const broker = new SimpleEventBroker({ ... });
|
|
31
|
+
*
|
|
32
|
+
* broker.subscribe('validation.complete', async (event) => {
|
|
33
|
+
* // Handle validation results
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* broker.subscribe('user.created', async (event) => {
|
|
37
|
+
* // Handle user creation side effects
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* await broker.publish({
|
|
41
|
+
* to: 'validation.complete',
|
|
42
|
+
* payload: userData
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class SimpleEventBroker {
|
|
47
|
+
private readonly subscribers;
|
|
48
|
+
private readonly queue;
|
|
49
|
+
readonly events: ArvoEvent[];
|
|
50
|
+
private readonly maxQueueSize;
|
|
51
|
+
private readonly onError;
|
|
52
|
+
private isProcessing;
|
|
53
|
+
private readonly eventProcessDelay;
|
|
54
|
+
constructor(options: EventBusOptions);
|
|
55
|
+
/**
|
|
56
|
+
* All event types that have registered listeners.
|
|
57
|
+
*/
|
|
58
|
+
get topics(): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Subscribe to a specific event type
|
|
61
|
+
* @param topic - Event type to subscribe to
|
|
62
|
+
* @param handler - Function to handle the event
|
|
63
|
+
* @param assertUnique - Asserts the uniqne-ness of the handler.
|
|
64
|
+
* If true, then only one handler per topic
|
|
65
|
+
* otherwise, throws error
|
|
66
|
+
* @returns Unsubscribe function
|
|
67
|
+
*/
|
|
68
|
+
subscribe(topic: string, handler: EventBusListener, assertUnique?: boolean): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Publish an event to subscribers
|
|
71
|
+
* @param event - Event to publish
|
|
72
|
+
* @throws Error if queue is full or event has no topic
|
|
73
|
+
*/
|
|
74
|
+
publish(event: ArvoEvent): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Current number of events in queue
|
|
77
|
+
*/
|
|
78
|
+
get queueLength(): number;
|
|
79
|
+
/**
|
|
80
|
+
* Number of subscribers for a given topic
|
|
81
|
+
*/
|
|
82
|
+
getSubscriberCount(topic: string): number;
|
|
83
|
+
/**
|
|
84
|
+
* Processes queued events asynchronously, ensuring sequential execution.
|
|
85
|
+
* Handles error cases and maintains the processing state.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* - Only one instance of processQueue runs at a time
|
|
89
|
+
* - Events are processed in FIFO order
|
|
90
|
+
* - Failed event handlers trigger onError callback
|
|
91
|
+
* - All handlers for an event are processed in parallel
|
|
92
|
+
*
|
|
93
|
+
* @throws Propagates any unhandled errors from event processing
|
|
94
|
+
*/
|
|
95
|
+
private processQueue;
|
|
96
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SimpleEventBroker = void 0;
|
|
40
|
+
var utils_1 = require("./utils");
|
|
41
|
+
/**
|
|
42
|
+
* A simple event broker for handling local event-driven workflows within function scope.
|
|
43
|
+
* Ideal for composing function handlers and coordinating local business logic steps.
|
|
44
|
+
*
|
|
45
|
+
* @description
|
|
46
|
+
* Use SimpleEventBroker when you need:
|
|
47
|
+
* - Local event handling within a function's execution scope
|
|
48
|
+
* - Decoupling steps in a business process
|
|
49
|
+
* - Simple composition of handlers for a workflow
|
|
50
|
+
* - In-memory event management for a single operation
|
|
51
|
+
*
|
|
52
|
+
* Not suitable for:
|
|
53
|
+
* - Long-running processes or persistent event storage
|
|
54
|
+
* - Cross-process or distributed event handling
|
|
55
|
+
* - High-throughput event processing (>1000s events/sec)
|
|
56
|
+
* - Mission critical or fault-tolerant systems
|
|
57
|
+
* - Complex event routing or filtering
|
|
58
|
+
*
|
|
59
|
+
* Typical use cases:
|
|
60
|
+
* - Coordinating steps in a registration process
|
|
61
|
+
* - Managing validation and processing workflows
|
|
62
|
+
* - Decoupling business logic steps
|
|
63
|
+
* - Local event-driven state management
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // During a registration flow
|
|
68
|
+
* const broker = new SimpleEventBroker({ ... });
|
|
69
|
+
*
|
|
70
|
+
* broker.subscribe('validation.complete', async (event) => {
|
|
71
|
+
* // Handle validation results
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* broker.subscribe('user.created', async (event) => {
|
|
75
|
+
* // Handle user creation side effects
|
|
76
|
+
* });
|
|
77
|
+
*
|
|
78
|
+
* await broker.publish({
|
|
79
|
+
* to: 'validation.complete',
|
|
80
|
+
* payload: userData
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
var SimpleEventBroker = /** @class */ (function () {
|
|
85
|
+
function SimpleEventBroker(options) {
|
|
86
|
+
this.eventProcessDelay = 1;
|
|
87
|
+
this.subscribers = new Map();
|
|
88
|
+
this.queue = [];
|
|
89
|
+
this.events = [];
|
|
90
|
+
this.isProcessing = false;
|
|
91
|
+
this.maxQueueSize = options.maxQueueSize;
|
|
92
|
+
this.onError = options.errorHandler;
|
|
93
|
+
}
|
|
94
|
+
Object.defineProperty(SimpleEventBroker.prototype, "topics", {
|
|
95
|
+
/**
|
|
96
|
+
* All event types that have registered listeners.
|
|
97
|
+
*/
|
|
98
|
+
get: function () {
|
|
99
|
+
return Array.from(this.subscribers.keys());
|
|
100
|
+
},
|
|
101
|
+
enumerable: false,
|
|
102
|
+
configurable: true
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Subscribe to a specific event type
|
|
106
|
+
* @param topic - Event type to subscribe to
|
|
107
|
+
* @param handler - Function to handle the event
|
|
108
|
+
* @param assertUnique - Asserts the uniqne-ness of the handler.
|
|
109
|
+
* If true, then only one handler per topic
|
|
110
|
+
* otherwise, throws error
|
|
111
|
+
* @returns Unsubscribe function
|
|
112
|
+
*/
|
|
113
|
+
SimpleEventBroker.prototype.subscribe = function (topic, handler, assertUnique) {
|
|
114
|
+
var _this = this;
|
|
115
|
+
var _a, _b;
|
|
116
|
+
if (assertUnique === void 0) { assertUnique = false; }
|
|
117
|
+
if (!this.subscribers.has(topic)) {
|
|
118
|
+
this.subscribers.set(topic, new Set());
|
|
119
|
+
}
|
|
120
|
+
if (assertUnique && ((_b = (_a = this.subscribers.get(topic)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 1) {
|
|
121
|
+
throw new Error("Only one subscriber allowed per topic: ".concat(topic));
|
|
122
|
+
}
|
|
123
|
+
// biome-ignore lint/style/noNonNullAssertion: non issue
|
|
124
|
+
var handlers = this.subscribers.get(topic);
|
|
125
|
+
handlers.add(handler);
|
|
126
|
+
return function () {
|
|
127
|
+
handlers.delete(handler);
|
|
128
|
+
if (handlers.size === 0) {
|
|
129
|
+
_this.subscribers.delete(topic);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Publish an event to subscribers
|
|
135
|
+
* @param event - Event to publish
|
|
136
|
+
* @throws Error if queue is full or event has no topic
|
|
137
|
+
*/
|
|
138
|
+
SimpleEventBroker.prototype.publish = function (event) {
|
|
139
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
140
|
+
return __generator(this, function (_a) {
|
|
141
|
+
switch (_a.label) {
|
|
142
|
+
case 0:
|
|
143
|
+
if (!event.to) {
|
|
144
|
+
throw new Error('Event must have a "to" property');
|
|
145
|
+
}
|
|
146
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
147
|
+
throw new Error("Event queue is full (max size: ".concat(this.maxQueueSize, ")"));
|
|
148
|
+
}
|
|
149
|
+
this.queue.push(event);
|
|
150
|
+
this.events.push(event);
|
|
151
|
+
if (!!this.isProcessing) return [3 /*break*/, 2];
|
|
152
|
+
return [4 /*yield*/, this.processQueue()];
|
|
153
|
+
case 1:
|
|
154
|
+
_a.sent();
|
|
155
|
+
_a.label = 2;
|
|
156
|
+
case 2: return [2 /*return*/];
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
Object.defineProperty(SimpleEventBroker.prototype, "queueLength", {
|
|
162
|
+
/**
|
|
163
|
+
* Current number of events in queue
|
|
164
|
+
*/
|
|
165
|
+
get: function () {
|
|
166
|
+
return this.queue.length;
|
|
167
|
+
},
|
|
168
|
+
enumerable: false,
|
|
169
|
+
configurable: true
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* Number of subscribers for a given topic
|
|
173
|
+
*/
|
|
174
|
+
SimpleEventBroker.prototype.getSubscriberCount = function (topic) {
|
|
175
|
+
var _a, _b;
|
|
176
|
+
return (_b = (_a = this.subscribers.get(topic)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Processes queued events asynchronously, ensuring sequential execution.
|
|
180
|
+
* Handles error cases and maintains the processing state.
|
|
181
|
+
*
|
|
182
|
+
* @remarks
|
|
183
|
+
* - Only one instance of processQueue runs at a time
|
|
184
|
+
* - Events are processed in FIFO order
|
|
185
|
+
* - Failed event handlers trigger onError callback
|
|
186
|
+
* - All handlers for an event are processed in parallel
|
|
187
|
+
*
|
|
188
|
+
* @throws Propagates any unhandled errors from event processing
|
|
189
|
+
*/
|
|
190
|
+
SimpleEventBroker.prototype.processQueue = function () {
|
|
191
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
192
|
+
var _loop_1, this_1;
|
|
193
|
+
var _this = this;
|
|
194
|
+
var _a;
|
|
195
|
+
return __generator(this, function (_b) {
|
|
196
|
+
switch (_b.label) {
|
|
197
|
+
case 0:
|
|
198
|
+
if (this.isProcessing)
|
|
199
|
+
return [2 /*return*/];
|
|
200
|
+
this.isProcessing = true;
|
|
201
|
+
_b.label = 1;
|
|
202
|
+
case 1:
|
|
203
|
+
_b.trys.push([1, , 5, 6]);
|
|
204
|
+
_loop_1 = function () {
|
|
205
|
+
var event_1, handlers, handlerPromises;
|
|
206
|
+
return __generator(this, function (_c) {
|
|
207
|
+
switch (_c.label) {
|
|
208
|
+
case 0:
|
|
209
|
+
event_1 = this_1.queue.pop();
|
|
210
|
+
if (!event_1)
|
|
211
|
+
return [2 /*return*/, "continue"];
|
|
212
|
+
if (!this_1.eventProcessDelay) return [3 /*break*/, 2];
|
|
213
|
+
return [4 /*yield*/, (0, utils_1.promiseTimeout)(this_1.eventProcessDelay)];
|
|
214
|
+
case 1:
|
|
215
|
+
_c.sent();
|
|
216
|
+
_c.label = 2;
|
|
217
|
+
case 2:
|
|
218
|
+
handlers = this_1.subscribers.get((_a = event_1.to) !== null && _a !== void 0 ? _a : '');
|
|
219
|
+
if (!(handlers === null || handlers === void 0 ? void 0 : handlers.size)) {
|
|
220
|
+
this_1.onError(new Error("No handlers registered for event type: ".concat(event_1.to)), event_1);
|
|
221
|
+
return [2 /*return*/, "continue"];
|
|
222
|
+
}
|
|
223
|
+
handlerPromises = Array.from(handlers).map(function (handler) {
|
|
224
|
+
return handler(event_1, _this.publish.bind(_this)).catch(function (error) {
|
|
225
|
+
if (error instanceof Error) {
|
|
226
|
+
_this.onError(error, event_1);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
_this.onError(new Error(String(error)), event_1);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
return [4 /*yield*/, Promise.all(handlerPromises)];
|
|
234
|
+
case 3:
|
|
235
|
+
_c.sent();
|
|
236
|
+
return [2 /*return*/];
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
this_1 = this;
|
|
241
|
+
_b.label = 2;
|
|
242
|
+
case 2:
|
|
243
|
+
if (!(this.queue.length > 0)) return [3 /*break*/, 4];
|
|
244
|
+
return [5 /*yield**/, _loop_1()];
|
|
245
|
+
case 3:
|
|
246
|
+
_b.sent();
|
|
247
|
+
return [3 /*break*/, 2];
|
|
248
|
+
case 4: return [3 /*break*/, 6];
|
|
249
|
+
case 5:
|
|
250
|
+
this.isProcessing = false;
|
|
251
|
+
return [7 /*endfinally*/];
|
|
252
|
+
case 6: return [2 /*return*/];
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
return SimpleEventBroker;
|
|
258
|
+
}());
|
|
259
|
+
exports.SimpleEventBroker = SimpleEventBroker;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ArvoEvent } from 'arvo-core';
|
|
2
|
+
export type EventBusListener = (event: ArvoEvent, publish: (event: ArvoEvent) => Promise<void>) => Promise<void>;
|
|
3
|
+
export interface EventBusOptions {
|
|
4
|
+
maxQueueSize: number;
|
|
5
|
+
errorHandler: (error: Error, event: ArvoEvent) => void;
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const promiseTimeout: (timeout?: number) => Promise<void>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.promiseTimeout = void 0;
|
|
4
|
+
var promiseTimeout = function (timeout) {
|
|
5
|
+
if (timeout === void 0) { timeout = 10; }
|
|
6
|
+
return new Promise(function (resolve) {
|
|
7
|
+
setTimeout(resolve, timeout);
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
exports.promiseTimeout = promiseTimeout;
|