arvo-event-handler 3.0.26 → 3.0.27
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/ArvoOrchestrationUtils/handlerErrors.d.ts +3 -2
- package/dist/ArvoOrchestrationUtils/handlerErrors.js +5 -3
- package/dist/ArvoOrchestrationUtils/orchestrationExecutionState.d.ts +5 -1
- package/dist/ArvoOrchestrationUtils/orchestrationExecutionState.js +2 -0
- package/dist/ArvoOrchestrationUtils/orchestrationExecutionWrapper/acquireLockWithValidation.d.ts +2 -1
- package/dist/ArvoOrchestrationUtils/orchestrationExecutionWrapper/acquireLockWithValidation.js +2 -2
- package/dist/ArvoOrchestrationUtils/orchestrationExecutionWrapper/index.d.ts +2 -2
- package/dist/ArvoOrchestrationUtils/orchestrationExecutionWrapper/index.js +31 -15
- package/dist/ArvoOrchestrator/index.js +5 -1
- package/dist/ArvoOrchestrator/types.d.ts +2 -1
- package/dist/ArvoResumable/index.js +5 -1
- package/dist/MachineMemory/Simple.d.ts +33 -9
- package/dist/MachineMemory/Simple.js +51 -16
- package/dist/MachineMemory/interface.d.ts +145 -46
- package/dist/SyncEventResource/index.d.ts +22 -8
- package/dist/SyncEventResource/index.js +65 -12
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/types.d.ts +40 -0
- package/dist/types.js +11 -0
- package/package.json +2 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Span } from '@opentelemetry/api';
|
|
2
2
|
import { type ArvoContract, type ArvoEvent, type ArvoSemanticVersion, type OpenTelemetryHeaders, type VersionedArvoContract, type ViolationError } from 'arvo-core';
|
|
3
3
|
import type { SyncEventResource } from '../SyncEventResource';
|
|
4
|
-
import type
|
|
4
|
+
import { type OrchestrationExecutionMemoryRecord } from './orchestrationExecutionState';
|
|
5
5
|
import { type ArvoOrchestrationHandlerType } from './types';
|
|
6
6
|
import type { NonEmptyArray } from '../types';
|
|
7
|
+
import { MachineMemoryMetadata } from '../MachineMemory/interface';
|
|
7
8
|
/**
|
|
8
9
|
* Parameters for creating system error events during orchestration failures.
|
|
9
10
|
*/
|
|
@@ -54,7 +55,7 @@ export declare const createSystemErrorEvents: ({ error, event, otelHeaders, orch
|
|
|
54
55
|
*/
|
|
55
56
|
export declare const handleOrchestrationErrors: (_handlerType: ArvoOrchestrationHandlerType, param: CreateSystemErrorEventsParams & {
|
|
56
57
|
syncEventResource: SyncEventResource<OrchestrationExecutionMemoryRecord<Record<string, any>>>;
|
|
57
|
-
}, span: Span) => Promise<{
|
|
58
|
+
}, metadata: MachineMemoryMetadata, span: Span) => Promise<{
|
|
58
59
|
errorToThrow: ViolationError;
|
|
59
60
|
events: null;
|
|
60
61
|
} | {
|
|
@@ -53,6 +53,7 @@ var arvo_core_1 = require("arvo-core");
|
|
|
53
53
|
var ArvoDomain_1 = require("../ArvoDomain");
|
|
54
54
|
var errors_1 = require("../errors");
|
|
55
55
|
var utils_1 = require("../utils");
|
|
56
|
+
var orchestrationExecutionState_1 = require("./orchestrationExecutionState");
|
|
56
57
|
var types_1 = require("./types");
|
|
57
58
|
/**
|
|
58
59
|
* Creates standardized system error events for orchestration failures.
|
|
@@ -136,7 +137,7 @@ exports.createSystemErrorEvents = createSystemErrorEvents;
|
|
|
136
137
|
*
|
|
137
138
|
* @returns Either the violation error to throw or system error events to emit
|
|
138
139
|
*/
|
|
139
|
-
var handleOrchestrationErrors = function (_handlerType, param, span) { return __awaiter(void 0, void 0, void 0, function () {
|
|
140
|
+
var handleOrchestrationErrors = function (_handlerType, param, metadata, span) { return __awaiter(void 0, void 0, void 0, function () {
|
|
140
141
|
var handlerType, error, errorEvents, _i, _a, _b, errEvtIdx, errEvt, _c, _d, _e, key, value;
|
|
141
142
|
return __generator(this, function (_f) {
|
|
142
143
|
switch (_f.label) {
|
|
@@ -170,10 +171,11 @@ var handleOrchestrationErrors = function (_handlerType, param, span) { return __
|
|
|
170
171
|
}
|
|
171
172
|
return [4 /*yield*/, param.syncEventResource
|
|
172
173
|
.persistState(param.event, {
|
|
173
|
-
|
|
174
|
+
__type: 'OrchestrationExecutionMemoryRecord',
|
|
175
|
+
executionStatus: orchestrationExecutionState_1.OrchestrationExecutionStatus.FAILURE,
|
|
174
176
|
subject: param.event.subject,
|
|
175
177
|
error: param.error,
|
|
176
|
-
}, null, span)
|
|
178
|
+
}, null, metadata, span)
|
|
177
179
|
.catch(function (e) {
|
|
178
180
|
(0, arvo_core_1.logToSpan)({
|
|
179
181
|
level: 'CRITICAL',
|
|
@@ -9,6 +9,8 @@ export declare const OrchestrationExecutionStatus: {
|
|
|
9
9
|
readonly NORMAL: "normal";
|
|
10
10
|
/** Orchestration has failed and entered terminal error state */
|
|
11
11
|
readonly FAILURE: "failure";
|
|
12
|
+
/** Orchestration is marked as done */
|
|
13
|
+
readonly DONE: "done";
|
|
12
14
|
};
|
|
13
15
|
/**
|
|
14
16
|
* Discriminated union representing persisted orchestration state.
|
|
@@ -26,8 +28,10 @@ export declare const OrchestrationExecutionStatus: {
|
|
|
26
28
|
* @template T - Custom state fields specific to the orchestration type
|
|
27
29
|
*/
|
|
28
30
|
export type OrchestrationExecutionMemoryRecord<T extends Record<string, unknown>> = (T & {
|
|
29
|
-
|
|
31
|
+
__type: 'OrchestrationExecutionMemoryRecord';
|
|
32
|
+
executionStatus: typeof OrchestrationExecutionStatus.NORMAL | typeof OrchestrationExecutionStatus.DONE;
|
|
30
33
|
}) | (Partial<T> & {
|
|
34
|
+
__type: 'OrchestrationExecutionMemoryRecord';
|
|
31
35
|
executionStatus: typeof OrchestrationExecutionStatus.FAILURE;
|
|
32
36
|
error: Error;
|
|
33
37
|
subject: string;
|
package/dist/ArvoOrchestrationUtils/orchestrationExecutionWrapper/acquireLockWithValidation.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Span } from '@opentelemetry/api';
|
|
|
2
2
|
import { type ArvoEvent } from 'arvo-core';
|
|
3
3
|
import type { SyncEventResource } from '../../SyncEventResource';
|
|
4
4
|
import type { AcquiredLockStatusType } from '../../SyncEventResource/types';
|
|
5
|
+
import { MachineMemoryMetadata } from '../../MachineMemory/interface';
|
|
5
6
|
/**
|
|
6
7
|
* Acquires an exclusive lock for event processing with validation.
|
|
7
8
|
*
|
|
@@ -9,4 +10,4 @@ import type { AcquiredLockStatusType } from '../../SyncEventResource/types';
|
|
|
9
10
|
* processing. Throws if lock cannot be acquired, preventing concurrent modifications.
|
|
10
11
|
* @throws {TransactionViolation} When lock cannot be acquired
|
|
11
12
|
*/
|
|
12
|
-
export declare const acquireLockWithValidation: (syncEventResource: SyncEventResource<Record<string, any>>, event: ArvoEvent, span: Span) => Promise<AcquiredLockStatusType>;
|
|
13
|
+
export declare const acquireLockWithValidation: (syncEventResource: SyncEventResource<Record<string, any>>, event: ArvoEvent, metadata: MachineMemoryMetadata, span: Span) => Promise<AcquiredLockStatusType>;
|
package/dist/ArvoOrchestrationUtils/orchestrationExecutionWrapper/acquireLockWithValidation.js
CHANGED
|
@@ -46,11 +46,11 @@ var error_1 = require("../error");
|
|
|
46
46
|
* processing. Throws if lock cannot be acquired, preventing concurrent modifications.
|
|
47
47
|
* @throws {TransactionViolation} When lock cannot be acquired
|
|
48
48
|
*/
|
|
49
|
-
var acquireLockWithValidation = function (syncEventResource, event, span) { return __awaiter(void 0, void 0, void 0, function () {
|
|
49
|
+
var acquireLockWithValidation = function (syncEventResource, event, metadata, span) { return __awaiter(void 0, void 0, void 0, function () {
|
|
50
50
|
var acquiredLock;
|
|
51
51
|
return __generator(this, function (_a) {
|
|
52
52
|
switch (_a.label) {
|
|
53
|
-
case 0: return [4 /*yield*/, syncEventResource.acquireLock(event, span)];
|
|
53
|
+
case 0: return [4 /*yield*/, syncEventResource.acquireLock(event, metadata, span)];
|
|
54
54
|
case 1:
|
|
55
55
|
acquiredLock = _a.sent();
|
|
56
56
|
if (acquiredLock === 'NOT_ACQUIRED') {
|
|
@@ -2,8 +2,8 @@ import { type Span } from '@opentelemetry/api';
|
|
|
2
2
|
import { type ArvoEvent, type ArvoOrchestrationSubjectContent, type ArvoOrchestratorContract, type ArvoSemanticVersion, type OpenTelemetryHeaders, type VersionedArvoContract } from 'arvo-core';
|
|
3
3
|
import type IArvoEventHandler from '../../IArvoEventHandler';
|
|
4
4
|
import type { SyncEventResource } from '../../SyncEventResource';
|
|
5
|
-
import type
|
|
6
|
-
import type
|
|
5
|
+
import { type ArvoEventHandlerOpenTelemetryOptions, type ArvoEventHandlerOtelSpanOptions, type NonEmptyArray } from '../../types';
|
|
6
|
+
import { type OrchestrationExecutionMemoryRecord } from '../orchestrationExecutionState';
|
|
7
7
|
import type { ArvoOrchestrationHandlerType } from '../types';
|
|
8
8
|
/**
|
|
9
9
|
* Configuration context for orchestration execution.
|
|
@@ -50,8 +50,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
50
50
|
exports.executeWithOrchestrationWrapper = exports.returnEventsWithLogging = void 0;
|
|
51
51
|
var api_1 = require("@opentelemetry/api");
|
|
52
52
|
var arvo_core_1 = require("arvo-core");
|
|
53
|
+
var types_1 = require("../../types");
|
|
53
54
|
var utils_1 = require("../../utils");
|
|
54
55
|
var handlerErrors_1 = require("../handlerErrors");
|
|
56
|
+
var orchestrationExecutionState_1 = require("../orchestrationExecutionState");
|
|
55
57
|
var acquireLockWithValidation_1 = require("./acquireLockWithValidation");
|
|
56
58
|
var validateAndParseSubject_1 = require("./validateAndParseSubject");
|
|
57
59
|
/**
|
|
@@ -90,7 +92,7 @@ var executeWithOrchestrationWrapper = function (_a, coreExecutionFn_1) { return
|
|
|
90
92
|
case 0:
|
|
91
93
|
otelConfig = (0, utils_1.createEventHandlerTelemetryConfig)(spanOptions.spanName({ selfContractUri: selfContract.uri, consumedEvent: event }), spanOptions, opentelemetry, event);
|
|
92
94
|
return [4 /*yield*/, arvo_core_1.ArvoOpenTelemetry.getInstance().startActiveSpan(__assign(__assign({}, otelConfig), { fn: function (span) { return __awaiter(void 0, void 0, void 0, function () {
|
|
93
|
-
var _i, _a, _b, key, value, otelHeaders, orchestrationParentSubject, initEventId, acquiredLock, parsedEventSubject, state, _c, emittables, newState, i, _d, _e, _f, key, value, error_1, _g, errorToThrow, errorEvents;
|
|
95
|
+
var _i, _a, _b, key, value, otelHeaders, orchestrationParentSubject, initEventId, acquiredLock, machineMemoryMetadata, parsedEventSubject, state, _c, emittables, newState, i, _d, _e, _f, key, value, error_1, _g, errorToThrow, errorEvents;
|
|
94
96
|
var _h, _j, _k, _l;
|
|
95
97
|
return __generator(this, function (_m) {
|
|
96
98
|
switch (_m.label) {
|
|
@@ -110,34 +112,42 @@ var executeWithOrchestrationWrapper = function (_a, coreExecutionFn_1) { return
|
|
|
110
112
|
orchestrationParentSubject = null;
|
|
111
113
|
initEventId = null;
|
|
112
114
|
acquiredLock = null;
|
|
115
|
+
machineMemoryMetadata = {
|
|
116
|
+
subject: event.subject,
|
|
117
|
+
source: source,
|
|
118
|
+
initiator: types_1.Materialized.pending(),
|
|
119
|
+
parentSubject: types_1.Materialized.pending(),
|
|
120
|
+
};
|
|
113
121
|
_m.label = 1;
|
|
114
122
|
case 1:
|
|
115
|
-
_m.trys.push([1,
|
|
123
|
+
_m.trys.push([1, 8, 10, 12]);
|
|
116
124
|
parsedEventSubject = (0, validateAndParseSubject_1.validateAndParseSubject)(event, source, syncEventResource, span, 'orchestrator');
|
|
117
125
|
if (!parsedEventSubject) {
|
|
118
126
|
return [2 /*return*/, (0, exports.returnEventsWithLogging)({ events: [] }, span)];
|
|
119
127
|
}
|
|
120
|
-
return [4 /*yield*/, (0, acquireLockWithValidation_1.acquireLockWithValidation)(syncEventResource, event, span)];
|
|
128
|
+
return [4 /*yield*/, (0, acquireLockWithValidation_1.acquireLockWithValidation)(syncEventResource, event, machineMemoryMetadata, span)];
|
|
121
129
|
case 2:
|
|
122
130
|
// Lock acquisition
|
|
123
131
|
acquiredLock = _m.sent();
|
|
124
|
-
return [4 /*yield*/, syncEventResource.acquireState(event, span)];
|
|
132
|
+
return [4 /*yield*/, syncEventResource.acquireState(event, machineMemoryMetadata, span)];
|
|
125
133
|
case 3:
|
|
126
134
|
state = _m.sent();
|
|
127
|
-
if ((state === null || state === void 0 ? void 0 : state.executionStatus) ===
|
|
135
|
+
if ((state === null || state === void 0 ? void 0 : state.executionStatus) === orchestrationExecutionState_1.OrchestrationExecutionStatus.FAILURE ||
|
|
136
|
+
(state === null || state === void 0 ? void 0 : state.executionStatus) === orchestrationExecutionState_1.OrchestrationExecutionStatus.DONE) {
|
|
128
137
|
span.setAttribute('arvo.handler.execution.status', state.executionStatus);
|
|
129
138
|
(0, arvo_core_1.logToSpan)({
|
|
130
139
|
level: 'WARNING',
|
|
131
|
-
message: "The orchestration
|
|
140
|
+
message: "The orchestration is in terminal state '".concat(state.executionStatus, "'. Ignoring event id: ").concat(event.id, " with event subject: ").concat(event.subject),
|
|
132
141
|
}, span);
|
|
133
142
|
span.setStatus({
|
|
134
|
-
code: api_1.SpanStatusCode.ERROR,
|
|
135
|
-
message: "The orchestration
|
|
143
|
+
code: state.executionStatus === orchestrationExecutionState_1.OrchestrationExecutionStatus.FAILURE ? api_1.SpanStatusCode.ERROR : api_1.SpanStatusCode.OK,
|
|
144
|
+
message: "The orchestration is in terminal state '".concat(state.executionStatus, "'. Ignoring event id: ").concat(event.id, " with event subject: ").concat(event.subject),
|
|
136
145
|
});
|
|
137
146
|
return [2 /*return*/, (0, exports.returnEventsWithLogging)({ events: [] }, span)];
|
|
138
147
|
}
|
|
139
148
|
orchestrationParentSubject = (_h = state === null || state === void 0 ? void 0 : state.parentSubject) !== null && _h !== void 0 ? _h : null;
|
|
140
149
|
initEventId = (_j = state === null || state === void 0 ? void 0 : state.initEventId) !== null && _j !== void 0 ? _j : null;
|
|
150
|
+
machineMemoryMetadata = __assign(__assign({}, machineMemoryMetadata), { initiator: types_1.Materialized.resolved(parsedEventSubject.execution.initiator), parentSubject: types_1.Materialized.resolved(orchestrationParentSubject) });
|
|
141
151
|
if (!state) {
|
|
142
152
|
(0, arvo_core_1.logToSpan)({
|
|
143
153
|
level: 'INFO',
|
|
@@ -180,10 +190,16 @@ var executeWithOrchestrationWrapper = function (_a, coreExecutionFn_1) { return
|
|
|
180
190
|
}
|
|
181
191
|
}
|
|
182
192
|
// Persist state
|
|
183
|
-
return [4 /*yield*/, syncEventResource.persistState(event, newState, state, span)];
|
|
193
|
+
return [4 /*yield*/, syncEventResource.persistState(event, newState, state, machineMemoryMetadata, span)];
|
|
184
194
|
case 5:
|
|
185
195
|
// Persist state
|
|
186
196
|
_m.sent();
|
|
197
|
+
if (!(newState.executionStatus === orchestrationExecutionState_1.OrchestrationExecutionStatus.DONE)) return [3 /*break*/, 7];
|
|
198
|
+
return [4 /*yield*/, syncEventResource.cleanup(event, newState, state, machineMemoryMetadata, span)];
|
|
199
|
+
case 6:
|
|
200
|
+
_m.sent();
|
|
201
|
+
_m.label = 7;
|
|
202
|
+
case 7:
|
|
187
203
|
(0, arvo_core_1.logToSpan)({
|
|
188
204
|
level: 'INFO',
|
|
189
205
|
message: "State update persisted in memory for subject ".concat(event.subject),
|
|
@@ -193,7 +209,7 @@ var executeWithOrchestrationWrapper = function (_a, coreExecutionFn_1) { return
|
|
|
193
209
|
message: "Execution successfully completed and emitted ".concat(emittables.length, " events"),
|
|
194
210
|
});
|
|
195
211
|
return [2 /*return*/, (0, exports.returnEventsWithLogging)({ events: emittables }, span)];
|
|
196
|
-
case
|
|
212
|
+
case 8:
|
|
197
213
|
error_1 = _m.sent();
|
|
198
214
|
span.setAttribute('arvo.handler.execution.status', 'failure');
|
|
199
215
|
return [4 /*yield*/, (0, handlerErrors_1.handleOrchestrationErrors)(_handlerType, {
|
|
@@ -208,20 +224,20 @@ var executeWithOrchestrationWrapper = function (_a, coreExecutionFn_1) { return
|
|
|
208
224
|
source: source,
|
|
209
225
|
syncEventResource: syncEventResource,
|
|
210
226
|
handlerType: _handlerType,
|
|
211
|
-
}, span)];
|
|
212
|
-
case
|
|
227
|
+
}, machineMemoryMetadata, span)];
|
|
228
|
+
case 9:
|
|
213
229
|
_g = _m.sent(), errorToThrow = _g.errorToThrow, errorEvents = _g.events;
|
|
214
230
|
if (errorToThrow)
|
|
215
231
|
throw errorToThrow;
|
|
216
232
|
return [2 /*return*/, {
|
|
217
233
|
events: errorEvents,
|
|
218
234
|
}];
|
|
219
|
-
case
|
|
220
|
-
case
|
|
235
|
+
case 10: return [4 /*yield*/, syncEventResource.releaseLock(event, acquiredLock, machineMemoryMetadata, span)];
|
|
236
|
+
case 11:
|
|
221
237
|
_m.sent();
|
|
222
238
|
span.end();
|
|
223
239
|
return [7 /*endfinally*/];
|
|
224
|
-
case
|
|
240
|
+
case 12: return [2 /*return*/];
|
|
225
241
|
}
|
|
226
242
|
});
|
|
227
243
|
}); } }))];
|
|
@@ -55,6 +55,7 @@ var orchestrationExecutionWrapper_1 = require("../ArvoOrchestrationUtils/orchest
|
|
|
55
55
|
var SyncEventResource_1 = require("../SyncEventResource");
|
|
56
56
|
var errors_1 = require("../errors");
|
|
57
57
|
var ArvoDomain_1 = require("../ArvoDomain");
|
|
58
|
+
var orchestrationExecutionState_1 = require("../ArvoOrchestrationUtils/orchestrationExecutionState");
|
|
58
59
|
/**
|
|
59
60
|
* Orchestrates state machine execution and lifecycle management.
|
|
60
61
|
*
|
|
@@ -206,7 +207,10 @@ var ArvoOrchestrator = /** @class */ (function () {
|
|
|
206
207
|
message: "Machine execution completed - Status: ".concat(executionResult.state.status, ", Generated events: ").concat(emittables.length),
|
|
207
208
|
}, span);
|
|
208
209
|
newState = {
|
|
209
|
-
|
|
210
|
+
__type: 'OrchestrationExecutionMemoryRecord',
|
|
211
|
+
executionStatus: executionResult.finalOutput
|
|
212
|
+
? orchestrationExecutionState_1.OrchestrationExecutionStatus.DONE
|
|
213
|
+
: orchestrationExecutionState_1.OrchestrationExecutionStatus.NORMAL,
|
|
210
214
|
initEventId: initEventId,
|
|
211
215
|
subject: event.subject,
|
|
212
216
|
parentSubject: orchestrationParentSubject,
|
|
@@ -135,7 +135,8 @@ export type ArvoOrchestratorParam = {
|
|
|
135
135
|
* Simplified interface for {@link createArvoOrchestrator} that automatically
|
|
136
136
|
* constructs default registry and execution engine components.
|
|
137
137
|
*/
|
|
138
|
-
export type CreateArvoOrchestratorParam = Pick<ArvoOrchestratorParam, '
|
|
138
|
+
export type CreateArvoOrchestratorParam = Pick<ArvoOrchestratorParam, 'executionunits' | 'spanOptions' | 'defaultEventEmissionDomains'> & {
|
|
139
|
+
memory: IMachineMemory<Record<string, any>>;
|
|
139
140
|
/**
|
|
140
141
|
* Optional override for resource locking requirement.
|
|
141
142
|
*
|
|
@@ -56,6 +56,7 @@ var orchestrationExecutionWrapper_1 = require("../ArvoOrchestrationUtils/orchest
|
|
|
56
56
|
var index_1 = require("../SyncEventResource/index");
|
|
57
57
|
var errors_1 = require("../errors");
|
|
58
58
|
var ArvoDomain_1 = require("../ArvoDomain");
|
|
59
|
+
var orchestrationExecutionState_1 = require("../ArvoOrchestrationUtils/orchestrationExecutionState");
|
|
59
60
|
/**
|
|
60
61
|
* ArvoResumable complements {@link ArvoOrchestrator} by providing imperative
|
|
61
62
|
* handler functions for orchestration logic instead of declarative state machines.
|
|
@@ -266,7 +267,10 @@ var ArvoResumable = /** @class */ (function () {
|
|
|
266
267
|
produced: emittables.map(function (item) { return item.toJSON(); }),
|
|
267
268
|
};
|
|
268
269
|
newState = {
|
|
269
|
-
|
|
270
|
+
__type: 'OrchestrationExecutionMemoryRecord',
|
|
271
|
+
executionStatus: (executionResult === null || executionResult === void 0 ? void 0 : executionResult.output)
|
|
272
|
+
? orchestrationExecutionState_1.OrchestrationExecutionStatus.DONE
|
|
273
|
+
: orchestrationExecutionState_1.OrchestrationExecutionStatus.NORMAL,
|
|
270
274
|
status: (executionResult === null || executionResult === void 0 ? void 0 : executionResult.output) ? 'done' : 'active',
|
|
271
275
|
initEventId: initEventId,
|
|
272
276
|
parentSubject: orchestrationParentSubject,
|
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
import type { IMachineMemory } from './interface';
|
|
2
2
|
/**
|
|
3
|
-
* In-memory implementation of machine state storage for single-instance NodeJS
|
|
4
|
-
*
|
|
5
|
-
* Best for: Container apps, request-scoped workflows, testing, demos
|
|
6
|
-
* Not for: Multi-instance deployments, persistent workflows, distributed systems
|
|
3
|
+
* In-memory implementation of machine state storage for single-instance NodeJS applications.
|
|
7
4
|
*
|
|
8
5
|
* @example
|
|
9
6
|
* const memory = new SimpleMachineMemory();
|
|
10
7
|
* const orchestrator = createArvoOrchestrator({
|
|
11
8
|
* memory,
|
|
12
|
-
* executionunits: 1,
|
|
13
9
|
* machines: [workflow]
|
|
14
10
|
* });
|
|
11
|
+
*
|
|
12
|
+
* @warning Concurrency Limitations
|
|
13
|
+
*
|
|
14
|
+
* This implementation is NOT concurrency-safe. Lock acquisition is not atomic,
|
|
15
|
+
* making it unsuitable for parallel event processing where multiple handlers
|
|
16
|
+
* might process events for the same workflow simultaneously.
|
|
17
|
+
*
|
|
18
|
+
* **Safe Usage:**
|
|
19
|
+
* - {@link SimpleEventBroker} (sequential event processing)
|
|
20
|
+
* - Isolated handlers without shared durable state requirements
|
|
21
|
+
*
|
|
22
|
+
* **Unsafe Usage:**
|
|
23
|
+
* - In-memory parallel/concurrent event brokers (e.g., p-queue with prefetch > 1)
|
|
24
|
+
*
|
|
25
|
+
* For concurrent in-process event processing, use the concurrency-safe memory
|
|
26
|
+
* backend from the `@arvo-tools/concurrent` package, which provides atomic lock
|
|
27
|
+
* operations suitable for parallel execution within a single process.
|
|
28
|
+
*
|
|
29
|
+
* **Unsuitable For:**
|
|
30
|
+
* - Multi-instance deployments
|
|
31
|
+
* - Distributed systems requiring shared state across processes
|
|
32
|
+
*
|
|
33
|
+
* For these scenarios, implement or use a database-backed memory backend
|
|
34
|
+
* (Redis, PostgreSQL, DynamoDB, etc.) that provides distributed state
|
|
35
|
+
* persistence and atomic locking across instances. You can also explore the
|
|
36
|
+
* Arvo tool eco-system `@arvo-tools`
|
|
15
37
|
*/
|
|
16
38
|
export declare class SimpleMachineMemory<T extends Record<string, any> = Record<string, any>> implements IMachineMemory<T> {
|
|
17
39
|
private readonly memoryMap;
|
|
18
40
|
private readonly lockMap;
|
|
41
|
+
private readonly enableCleanup;
|
|
42
|
+
constructor(config?: {
|
|
43
|
+
enableCleanup?: boolean;
|
|
44
|
+
});
|
|
19
45
|
/**
|
|
20
46
|
* Gets stored state for a machine instance
|
|
21
47
|
* @param id Machine instance ID
|
|
@@ -44,8 +70,6 @@ export declare class SimpleMachineMemory<T extends Record<string, any> = Record<
|
|
|
44
70
|
* @throws {Error} When id is empty or undefined
|
|
45
71
|
*/
|
|
46
72
|
unlock(id: string): Promise<boolean>;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/
|
|
50
|
-
clear(key?: string): void;
|
|
73
|
+
cleanup(id: string): Promise<void>;
|
|
74
|
+
clear(): void;
|
|
51
75
|
}
|
|
@@ -48,24 +48,50 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
48
48
|
};
|
|
49
49
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
50
|
exports.SimpleMachineMemory = void 0;
|
|
51
|
+
var arvo_core_1 = require("arvo-core");
|
|
51
52
|
/**
|
|
52
|
-
* In-memory implementation of machine state storage for single-instance NodeJS
|
|
53
|
-
*
|
|
54
|
-
* Best for: Container apps, request-scoped workflows, testing, demos
|
|
55
|
-
* Not for: Multi-instance deployments, persistent workflows, distributed systems
|
|
53
|
+
* In-memory implementation of machine state storage for single-instance NodeJS applications.
|
|
56
54
|
*
|
|
57
55
|
* @example
|
|
58
56
|
* const memory = new SimpleMachineMemory();
|
|
59
57
|
* const orchestrator = createArvoOrchestrator({
|
|
60
58
|
* memory,
|
|
61
|
-
* executionunits: 1,
|
|
62
59
|
* machines: [workflow]
|
|
63
60
|
* });
|
|
61
|
+
*
|
|
62
|
+
* @warning Concurrency Limitations
|
|
63
|
+
*
|
|
64
|
+
* This implementation is NOT concurrency-safe. Lock acquisition is not atomic,
|
|
65
|
+
* making it unsuitable for parallel event processing where multiple handlers
|
|
66
|
+
* might process events for the same workflow simultaneously.
|
|
67
|
+
*
|
|
68
|
+
* **Safe Usage:**
|
|
69
|
+
* - {@link SimpleEventBroker} (sequential event processing)
|
|
70
|
+
* - Isolated handlers without shared durable state requirements
|
|
71
|
+
*
|
|
72
|
+
* **Unsafe Usage:**
|
|
73
|
+
* - In-memory parallel/concurrent event brokers (e.g., p-queue with prefetch > 1)
|
|
74
|
+
*
|
|
75
|
+
* For concurrent in-process event processing, use the concurrency-safe memory
|
|
76
|
+
* backend from the `@arvo-tools/concurrent` package, which provides atomic lock
|
|
77
|
+
* operations suitable for parallel execution within a single process.
|
|
78
|
+
*
|
|
79
|
+
* **Unsuitable For:**
|
|
80
|
+
* - Multi-instance deployments
|
|
81
|
+
* - Distributed systems requiring shared state across processes
|
|
82
|
+
*
|
|
83
|
+
* For these scenarios, implement or use a database-backed memory backend
|
|
84
|
+
* (Redis, PostgreSQL, DynamoDB, etc.) that provides distributed state
|
|
85
|
+
* persistence and atomic locking across instances. You can also explore the
|
|
86
|
+
* Arvo tool eco-system `@arvo-tools`
|
|
64
87
|
*/
|
|
65
88
|
var SimpleMachineMemory = /** @class */ (function () {
|
|
66
|
-
function SimpleMachineMemory() {
|
|
89
|
+
function SimpleMachineMemory(config) {
|
|
90
|
+
var _a;
|
|
67
91
|
this.memoryMap = new Map();
|
|
68
92
|
this.lockMap = new Map();
|
|
93
|
+
this.enableCleanup = true;
|
|
94
|
+
this.enableCleanup = (_a = config === null || config === void 0 ? void 0 : config.enableCleanup) !== null && _a !== void 0 ? _a : true;
|
|
69
95
|
}
|
|
70
96
|
/**
|
|
71
97
|
* Gets stored state for a machine instance
|
|
@@ -141,17 +167,26 @@ var SimpleMachineMemory = /** @class */ (function () {
|
|
|
141
167
|
});
|
|
142
168
|
});
|
|
143
169
|
};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
170
|
+
SimpleMachineMemory.prototype.cleanup = function (id) {
|
|
171
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
172
|
+
return __generator(this, function (_a) {
|
|
173
|
+
if (!this.enableCleanup) {
|
|
174
|
+
(0, arvo_core_1.logToSpan)({
|
|
175
|
+
level: 'INFO',
|
|
176
|
+
message: 'Skipping cleanup due to config setting',
|
|
177
|
+
});
|
|
178
|
+
return [2 /*return*/];
|
|
179
|
+
}
|
|
180
|
+
this.memoryMap.delete(id);
|
|
181
|
+
this.lockMap.delete(id);
|
|
182
|
+
return [2 /*return*/];
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
// Clears all stored data and locks
|
|
187
|
+
SimpleMachineMemory.prototype.clear = function () {
|
|
154
188
|
this.lockMap.clear();
|
|
189
|
+
this.memoryMap.clear();
|
|
155
190
|
};
|
|
156
191
|
return SimpleMachineMemory;
|
|
157
192
|
}());
|
|
@@ -1,57 +1,156 @@
|
|
|
1
|
+
import { Materializable } from '../types';
|
|
2
|
+
export type MachineMemoryMetadata = {
|
|
3
|
+
/**
|
|
4
|
+
* Workflow instance identifier (same as event.subject).
|
|
5
|
+
* This unique identifier remains constant throughout the workflow's entire lifecycle.
|
|
6
|
+
*/
|
|
7
|
+
subject: string;
|
|
8
|
+
/**
|
|
9
|
+
* Parent workflow's subject for tracking orchestration hierarchies.
|
|
10
|
+
*
|
|
11
|
+
* When materialized, a null value indicates this is a root workflow with no parent.
|
|
12
|
+
* A string value indicates this is a child workflow with the specified parent subject.
|
|
13
|
+
* A pending state means the parent context has not yet been determined.
|
|
14
|
+
*
|
|
15
|
+
* This enables hierarchical workflow tracking and cleanup strategies that differentiate
|
|
16
|
+
* between root workflows, child workflows, and workflows with pending parent determination.
|
|
17
|
+
*/
|
|
18
|
+
parentSubject: Materializable<string | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Identifier of the entity or process that initiated this workflow.
|
|
21
|
+
*
|
|
22
|
+
* When materialized, contains the initiator's identifier.
|
|
23
|
+
* A pending state means the initiator has not yet been determined.
|
|
24
|
+
*/
|
|
25
|
+
initiator: Materializable<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Orchestrator's source identifier (handler.source).
|
|
28
|
+
* Identifies which orchestrator type is managing this workflow instance.
|
|
29
|
+
*/
|
|
30
|
+
source: string;
|
|
31
|
+
};
|
|
1
32
|
/**
|
|
2
|
-
* Manages
|
|
3
|
-
*
|
|
4
|
-
*
|
|
33
|
+
* Manages workflow instance state with optimistic locking for orchestration handlers.
|
|
34
|
+
*
|
|
35
|
+
* Each workflow execution has a unique identifier (event.subject) that remains constant
|
|
36
|
+
* throughout its entire lifecycle. This interface uses that subject as the key for all
|
|
37
|
+
* state operations, enabling multiple concurrent executions of the same orchestrator
|
|
38
|
+
* to maintain isolated state.
|
|
39
|
+
*
|
|
40
|
+
* Implements "fail fast on acquire, be tolerant on release" locking philosophy:
|
|
41
|
+
* - Lock acquisition fails quickly after reasonable retries to prevent duplicate execution
|
|
42
|
+
* - Lock release tolerates failures since TTL-based expiry provides automatic recovery
|
|
43
|
+
*
|
|
44
|
+
* @template T - Structure of the workflow state data stored per instance
|
|
5
45
|
*/
|
|
6
|
-
export interface IMachineMemory<T extends Record<string, any>> {
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* @param
|
|
20
|
-
* @returns null if no
|
|
21
|
-
* @throws Error if read operation fails after retries (
|
|
22
|
-
*/
|
|
23
|
-
read(id: string): Promise<T | null>;
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
46
|
+
export interface IMachineMemory<T extends Record<string, any> = Record<string, any>> {
|
|
47
|
+
/**
|
|
48
|
+
* Retrieves persisted state for a specific workflow instance.
|
|
49
|
+
*
|
|
50
|
+
* The orchestrator calls this when resuming a workflow to load the context
|
|
51
|
+
* needed to continue from where it previously stopped. Returns null for
|
|
52
|
+
* new workflow instances that have no persisted state yet.
|
|
53
|
+
*
|
|
54
|
+
* Should implement minimal retries (2-3 attempts) with short backoff for
|
|
55
|
+
* transient failures to avoid blocking event processing. Total operation
|
|
56
|
+
* time should stay under 1 second.
|
|
57
|
+
*
|
|
58
|
+
* @param id - Workflow instance identifier (event.subject)
|
|
59
|
+
* @param metadata - Workflow context (subject, parent subject, source)
|
|
60
|
+
* @returns null if no state exists for this workflow instance, T if state found
|
|
61
|
+
* @throws Error if read operation fails after retries (connection error, permission denied, etc.)
|
|
62
|
+
*/
|
|
63
|
+
read(id: string, metadata: MachineMemoryMetadata | null): Promise<T | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Persists updated state for a specific workflow instance.
|
|
66
|
+
*
|
|
67
|
+
* Called after the orchestrator processes an event and needs to save
|
|
68
|
+
* the workflow's current context before terminating. The next event
|
|
69
|
+
* for this workflow will load this saved state to resume execution.
|
|
70
|
+
*
|
|
71
|
+
* Should fail fast without retries - if persistence fails, the orchestrator
|
|
72
|
+
* must know immediately to avoid state inconsistency. The caller handles
|
|
73
|
+
* failures through retry mechanisms or dead letter queues.
|
|
74
|
+
*
|
|
75
|
+
* @param id - Workflow instance identifier (event.subject)
|
|
76
|
+
* @param data - Current workflow state to persist
|
|
77
|
+
* @param prevData - Previous state snapshot (can be used for compare-and-swap or audit trails)
|
|
78
|
+
* @param metadata - Workflow context (subject, parent subject, source)
|
|
31
79
|
* @throws Error if write operation fails
|
|
32
80
|
*/
|
|
33
|
-
write(id: string, data: T, prevData: T | null): Promise<void>;
|
|
81
|
+
write(id: string, data: T, prevData: T | null, metadata: MachineMemoryMetadata | null): Promise<void>;
|
|
34
82
|
/**
|
|
35
|
-
* Acquires execution lock for
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
83
|
+
* Acquires exclusive execution lock for a workflow instance.
|
|
84
|
+
*
|
|
85
|
+
* Prevents concurrent processing of the same workflow instance across
|
|
86
|
+
* distributed orchestrator instances. The orchestrator acquires this lock
|
|
87
|
+
* before reading/modifying state to ensure only one instance processes
|
|
88
|
+
* events for this workflow at any given time.
|
|
89
|
+
*
|
|
90
|
+
* Should implement reasonable retries (2-3 attempts) with backoff for
|
|
91
|
+
* transient lock conflicts, then fail fast. Returns false if another
|
|
92
|
+
* instance holds the lock after retries exhausted.
|
|
93
|
+
*
|
|
94
|
+
* CRITICAL: Should set TTL when acquiring lock (typically 30s-5m based on
|
|
95
|
+
* expected execution duration) to prevent permanent deadlocks if unlock fails.
|
|
96
|
+
*
|
|
97
|
+
* @param id - Workflow instance identifier (event.subject)
|
|
98
|
+
* @param metadata - Workflow context (subject, parent subject, source)
|
|
99
|
+
* @returns true if lock acquired successfully, false if lock held by another instance
|
|
100
|
+
* @throws Error if lock operation itself fails (not same as lock being unavailable)
|
|
41
101
|
*/
|
|
42
|
-
lock(id: string): Promise<boolean>;
|
|
102
|
+
lock(id: string, metadata: MachineMemoryMetadata | null): Promise<boolean>;
|
|
43
103
|
/**
|
|
44
|
-
* Releases execution lock for
|
|
45
|
-
*
|
|
46
|
-
*
|
|
104
|
+
* Releases execution lock for a workflow instance.
|
|
105
|
+
*
|
|
106
|
+
* Called after the orchestrator finishes processing an event and persisting
|
|
107
|
+
* state. Can retry a few times on transient failures but should not block
|
|
108
|
+
* execution - the TTL-based expiry ensures eventual recovery even if unlock fails.
|
|
109
|
+
*
|
|
110
|
+
* The "be tolerant on release" philosophy means unlock failures don't cascade
|
|
111
|
+
* into workflow failures. The lock will auto-expire via TTL, allowing the
|
|
112
|
+
* workflow to resume after the timeout period.
|
|
113
|
+
*
|
|
114
|
+
* @param id - Workflow instance identifier (event.subject)
|
|
115
|
+
* @param metadata - Workflow context (subject, parent subject, source)
|
|
116
|
+
* @returns true if unlocked successfully, false if unlock failed (non-critical)
|
|
117
|
+
*/
|
|
118
|
+
unlock(id: string, metadata: MachineMemoryMetadata | null): Promise<boolean>;
|
|
119
|
+
/**
|
|
120
|
+
* Optional cleanup hook invoked during successful workflow completion.
|
|
121
|
+
*
|
|
122
|
+
* The orchestrator calls this automatically when a workflow instance:
|
|
123
|
+
* 1. Reaches a final completion state (terminal state or returns output)
|
|
124
|
+
* 2. Successfully persists the final workflow state to storage
|
|
125
|
+
*
|
|
126
|
+
* This executes AFTER final state persistence but BEFORE the completion
|
|
127
|
+
* event is emitted back to the initiator. This ordering ensures the final
|
|
128
|
+
* state is durably saved before cleanup runs, while allowing cleanup logic
|
|
129
|
+
* to complete before the workflow signals completion to external systems.
|
|
130
|
+
*
|
|
131
|
+
* IMPORTANT: This hook is NOT called when orchestration execution fails
|
|
132
|
+
* (e.g., lock acquisition failure, state persistence failure, handler errors).
|
|
133
|
+
* It only executes for workflows that successfully reach their final state.
|
|
134
|
+
*
|
|
135
|
+
* Receives the same state transition data as write() to enable intelligent
|
|
136
|
+
* cleanup decisions based on how the workflow completed and what changed
|
|
137
|
+
* in the final step.
|
|
138
|
+
*
|
|
139
|
+
* This hook enables custom memory management strategies:
|
|
140
|
+
* - Mark state as eligible for garbage collection based on final state values
|
|
141
|
+
* - Archive completed workflows to cold storage with state-dependent retention
|
|
142
|
+
* - Implement conditional retention policies (e.g., keep failures longer than successes)
|
|
143
|
+
* - Extract specific data from final state for long-term analytics storage
|
|
144
|
+
* - Compare final vs previous state to determine appropriate storage tier
|
|
145
|
+
* - Trigger external cleanup processes with workflow completion context
|
|
47
146
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* - Ensure locks auto-expire to prevent deadlocks from unlock failures
|
|
51
|
-
* - Consider execution patterns when setting TTL to avoid premature expiry
|
|
147
|
+
* Implementations are not required to delete state immediately - they can
|
|
148
|
+
* implement whatever retention/archival strategy suits their operational needs.
|
|
52
149
|
*
|
|
53
|
-
* @param id -
|
|
54
|
-
* @
|
|
150
|
+
* @param id - Workflow instance identifier (event.subject)
|
|
151
|
+
* @param data - Final workflow state that was just persisted
|
|
152
|
+
* @param prevData - Previous state before final persistence (null if this was first/only state)
|
|
153
|
+
* @param metadata - Workflow context (subject, parent subject, source)
|
|
55
154
|
*/
|
|
56
|
-
|
|
155
|
+
cleanup?(id: string, data: T, prevData: T | null, metadata: MachineMemoryMetadata | null): Promise<void>;
|
|
57
156
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Span } from '@opentelemetry/api';
|
|
2
2
|
import { type ArvoEvent } from 'arvo-core';
|
|
3
|
-
import type { IMachineMemory } from '../MachineMemory/interface';
|
|
3
|
+
import type { IMachineMemory, MachineMemoryMetadata } from '../MachineMemory/interface';
|
|
4
4
|
import type { AcquiredLockStatusType, ReleasedLockStatusType } from './types';
|
|
5
5
|
/**
|
|
6
6
|
* A synchronous event resource that manages machine memory state based on event subjects.
|
|
@@ -52,7 +52,7 @@ export declare class SyncEventResource<T extends Record<string, any>> {
|
|
|
52
52
|
*
|
|
53
53
|
* @throws {TransactionViolation} When lock acquisition fails due to system errors
|
|
54
54
|
*/
|
|
55
|
-
acquireLock(event: ArvoEvent, span?: Span): Promise<AcquiredLockStatusType>;
|
|
55
|
+
acquireLock(event: ArvoEvent, metadata: MachineMemoryMetadata, span?: Span): Promise<AcquiredLockStatusType>;
|
|
56
56
|
/**
|
|
57
57
|
* Retrieves the current state from memory for the given event subject.
|
|
58
58
|
*
|
|
@@ -65,7 +65,7 @@ export declare class SyncEventResource<T extends Record<string, any>> {
|
|
|
65
65
|
*
|
|
66
66
|
* @throws {TransactionViolation} When the read operation fails due to storage errors
|
|
67
67
|
*/
|
|
68
|
-
acquireState(event: ArvoEvent, span?: Span): Promise<T | null>;
|
|
68
|
+
acquireState(event: ArvoEvent, metadata: MachineMemoryMetadata, span?: Span): Promise<T | null>;
|
|
69
69
|
/**
|
|
70
70
|
* Persists the updated memory state to distributed storage.
|
|
71
71
|
*
|
|
@@ -76,7 +76,7 @@ export declare class SyncEventResource<T extends Record<string, any>> {
|
|
|
76
76
|
*
|
|
77
77
|
* @throws {TransactionViolation} When the write operation fails due to storage errors
|
|
78
78
|
*/
|
|
79
|
-
persistState(event: ArvoEvent, record: T, prevRecord: T | null, span?: Span): Promise<void>;
|
|
79
|
+
persistState(event: ArvoEvent, record: T, prevRecord: T | null, metadata: MachineMemoryMetadata, span?: Span): Promise<void>;
|
|
80
80
|
/**
|
|
81
81
|
* Validates that the event subject conforms to the ArvoOrchestrationSubject format.
|
|
82
82
|
*
|
|
@@ -86,8 +86,6 @@ export declare class SyncEventResource<T extends Record<string, any>> {
|
|
|
86
86
|
* distributed service architecture.
|
|
87
87
|
*
|
|
88
88
|
* @throws {ExecutionViolation} When the event subject format is invalid
|
|
89
|
-
*
|
|
90
|
-
* @protected
|
|
91
89
|
*/
|
|
92
90
|
validateEventSubject(event: ArvoEvent, span?: Span): void;
|
|
93
91
|
/**
|
|
@@ -103,8 +101,24 @@ export declare class SyncEventResource<T extends Record<string, any>> {
|
|
|
103
101
|
* - 'NOOP': No lock was acquired, so no operation was performed
|
|
104
102
|
* - 'RELEASED': Lock was successfully released
|
|
105
103
|
* - 'ERROR': Lock release failed, potential resource leak
|
|
104
|
+
*/
|
|
105
|
+
releaseLock(event: ArvoEvent, acquiredLock: AcquiredLockStatusType | null, metadata: MachineMemoryMetadata, span?: Span): Promise<ReleasedLockStatusType>;
|
|
106
|
+
/**
|
|
107
|
+
* Invokes the optional cleanup hook after successful workflow completion.
|
|
108
|
+
*
|
|
109
|
+
* This method calls the memory implementation's cleanup hook if it exists, allowing
|
|
110
|
+
* implementations to perform custom memory management operations like archiving,
|
|
111
|
+
* marking for garbage collection, or implementing retention policies.
|
|
112
|
+
*
|
|
113
|
+
* Cleanup is a non-critical operation - failures are logged but do not throw exceptions
|
|
114
|
+
* or disrupt the workflow completion process. The assumption is that cleanup operations
|
|
115
|
+
* can be retried later or handled through separate maintenance processes.
|
|
106
116
|
*
|
|
107
|
-
* @
|
|
117
|
+
* @param event - The ArvoEvent that triggered workflow completion
|
|
118
|
+
* @param record - Final workflow state that was just persisted
|
|
119
|
+
* @param prevRecord - Previous state before final persistence
|
|
120
|
+
* @param metadata - Workflow context (subject, parent subject, source)
|
|
121
|
+
* @param span - Optional OpenTelemetry span for observability
|
|
108
122
|
*/
|
|
109
|
-
|
|
123
|
+
cleanup(event: ArvoEvent, record: T, prevRecord: T | null, metadata: MachineMemoryMetadata, span?: Span): Promise<void>;
|
|
110
124
|
}
|
|
@@ -91,7 +91,7 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
91
91
|
*
|
|
92
92
|
* @throws {TransactionViolation} When lock acquisition fails due to system errors
|
|
93
93
|
*/
|
|
94
|
-
SyncEventResource.prototype.acquireLock = function (event, span) {
|
|
94
|
+
SyncEventResource.prototype.acquireLock = function (event, metadata, span) {
|
|
95
95
|
return __awaiter(this, void 0, void 0, function () {
|
|
96
96
|
var acquired, e_1;
|
|
97
97
|
return __generator(this, function (_a) {
|
|
@@ -111,7 +111,7 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
111
111
|
level: 'INFO',
|
|
112
112
|
message: 'Acquiring lock for the event',
|
|
113
113
|
});
|
|
114
|
-
return [4 /*yield*/, this.memory.lock(event.subject)];
|
|
114
|
+
return [4 /*yield*/, this.memory.lock(event.subject, metadata)];
|
|
115
115
|
case 2:
|
|
116
116
|
acquired = _a.sent();
|
|
117
117
|
return [2 /*return*/, acquired ? 'ACQUIRED' : 'NOT_ACQUIRED'];
|
|
@@ -139,7 +139,7 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
139
139
|
*
|
|
140
140
|
* @throws {TransactionViolation} When the read operation fails due to storage errors
|
|
141
141
|
*/
|
|
142
|
-
SyncEventResource.prototype.acquireState = function (event, span) {
|
|
142
|
+
SyncEventResource.prototype.acquireState = function (event, metadata, span) {
|
|
143
143
|
return __awaiter(this, void 0, void 0, function () {
|
|
144
144
|
var e_2;
|
|
145
145
|
return __generator(this, function (_a) {
|
|
@@ -150,7 +150,7 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
150
150
|
level: 'INFO',
|
|
151
151
|
message: 'Reading machine state for the event',
|
|
152
152
|
}, span);
|
|
153
|
-
return [4 /*yield*/, this.memory.read(event.subject)];
|
|
153
|
+
return [4 /*yield*/, this.memory.read(event.subject, metadata)];
|
|
154
154
|
case 1: return [2 /*return*/, _a.sent()];
|
|
155
155
|
case 2:
|
|
156
156
|
e_2 = _a.sent();
|
|
@@ -174,7 +174,7 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
174
174
|
*
|
|
175
175
|
* @throws {TransactionViolation} When the write operation fails due to storage errors
|
|
176
176
|
*/
|
|
177
|
-
SyncEventResource.prototype.persistState = function (event, record, prevRecord, span) {
|
|
177
|
+
SyncEventResource.prototype.persistState = function (event, record, prevRecord, metadata, span) {
|
|
178
178
|
return __awaiter(this, void 0, void 0, function () {
|
|
179
179
|
var e_3;
|
|
180
180
|
return __generator(this, function (_a) {
|
|
@@ -185,7 +185,7 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
185
185
|
level: 'INFO',
|
|
186
186
|
message: 'Persisting machine state to the storage',
|
|
187
187
|
}, span);
|
|
188
|
-
return [4 /*yield*/, this.memory.write(event.subject, record, prevRecord)];
|
|
188
|
+
return [4 /*yield*/, this.memory.write(event.subject, record, prevRecord, metadata)];
|
|
189
189
|
case 1:
|
|
190
190
|
_a.sent();
|
|
191
191
|
return [3 /*break*/, 3];
|
|
@@ -210,8 +210,6 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
210
210
|
* distributed service architecture.
|
|
211
211
|
*
|
|
212
212
|
* @throws {ExecutionViolation} When the event subject format is invalid
|
|
213
|
-
*
|
|
214
|
-
* @protected
|
|
215
213
|
*/
|
|
216
214
|
SyncEventResource.prototype.validateEventSubject = function (event, span) {
|
|
217
215
|
(0, arvo_core_1.logToSpan)({
|
|
@@ -236,10 +234,8 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
236
234
|
* - 'NOOP': No lock was acquired, so no operation was performed
|
|
237
235
|
* - 'RELEASED': Lock was successfully released
|
|
238
236
|
* - 'ERROR': Lock release failed, potential resource leak
|
|
239
|
-
*
|
|
240
|
-
* @protected
|
|
241
237
|
*/
|
|
242
|
-
SyncEventResource.prototype.releaseLock = function (event, acquiredLock, span) {
|
|
238
|
+
SyncEventResource.prototype.releaseLock = function (event, acquiredLock, metadata, span) {
|
|
243
239
|
return __awaiter(this, void 0, void 0, function () {
|
|
244
240
|
var err_1;
|
|
245
241
|
return __generator(this, function (_a) {
|
|
@@ -255,7 +251,7 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
255
251
|
_a.label = 1;
|
|
256
252
|
case 1:
|
|
257
253
|
_a.trys.push([1, 3, , 4]);
|
|
258
|
-
return [4 /*yield*/, this.memory.unlock(event.subject)];
|
|
254
|
+
return [4 /*yield*/, this.memory.unlock(event.subject, metadata)];
|
|
259
255
|
case 2:
|
|
260
256
|
_a.sent();
|
|
261
257
|
(0, arvo_core_1.logToSpan)({
|
|
@@ -275,6 +271,63 @@ var SyncEventResource = /** @class */ (function () {
|
|
|
275
271
|
});
|
|
276
272
|
});
|
|
277
273
|
};
|
|
274
|
+
/**
|
|
275
|
+
* Invokes the optional cleanup hook after successful workflow completion.
|
|
276
|
+
*
|
|
277
|
+
* This method calls the memory implementation's cleanup hook if it exists, allowing
|
|
278
|
+
* implementations to perform custom memory management operations like archiving,
|
|
279
|
+
* marking for garbage collection, or implementing retention policies.
|
|
280
|
+
*
|
|
281
|
+
* Cleanup is a non-critical operation - failures are logged but do not throw exceptions
|
|
282
|
+
* or disrupt the workflow completion process. The assumption is that cleanup operations
|
|
283
|
+
* can be retried later or handled through separate maintenance processes.
|
|
284
|
+
*
|
|
285
|
+
* @param event - The ArvoEvent that triggered workflow completion
|
|
286
|
+
* @param record - Final workflow state that was just persisted
|
|
287
|
+
* @param prevRecord - Previous state before final persistence
|
|
288
|
+
* @param metadata - Workflow context (subject, parent subject, source)
|
|
289
|
+
* @param span - Optional OpenTelemetry span for observability
|
|
290
|
+
*/
|
|
291
|
+
SyncEventResource.prototype.cleanup = function (event, record, prevRecord, metadata, span) {
|
|
292
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
293
|
+
var err_2;
|
|
294
|
+
return __generator(this, function (_a) {
|
|
295
|
+
switch (_a.label) {
|
|
296
|
+
case 0:
|
|
297
|
+
if (!this.memory.cleanup) {
|
|
298
|
+
(0, arvo_core_1.logToSpan)({
|
|
299
|
+
level: 'INFO',
|
|
300
|
+
message: 'Cleanup hook not implemented by memory backend, skipping cleanup operation',
|
|
301
|
+
}, span);
|
|
302
|
+
return [2 /*return*/];
|
|
303
|
+
}
|
|
304
|
+
_a.label = 1;
|
|
305
|
+
case 1:
|
|
306
|
+
_a.trys.push([1, 3, , 4]);
|
|
307
|
+
(0, arvo_core_1.logToSpan)({
|
|
308
|
+
level: 'INFO',
|
|
309
|
+
message: 'Invoking cleanup hook for completed workflow',
|
|
310
|
+
}, span);
|
|
311
|
+
return [4 /*yield*/, this.memory.cleanup(event.subject, record, prevRecord, metadata)];
|
|
312
|
+
case 2:
|
|
313
|
+
_a.sent();
|
|
314
|
+
(0, arvo_core_1.logToSpan)({
|
|
315
|
+
level: 'INFO',
|
|
316
|
+
message: 'Cleanup hook completed successfully',
|
|
317
|
+
}, span);
|
|
318
|
+
return [3 /*break*/, 4];
|
|
319
|
+
case 3:
|
|
320
|
+
err_2 = _a.sent();
|
|
321
|
+
(0, arvo_core_1.logToSpan)({
|
|
322
|
+
level: 'ERROR',
|
|
323
|
+
message: "Cleanup operation failed (non-critical): ".concat(err_2.message),
|
|
324
|
+
}, span);
|
|
325
|
+
return [3 /*break*/, 4];
|
|
326
|
+
case 4: return [2 /*return*/];
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
};
|
|
278
331
|
return SyncEventResource;
|
|
279
332
|
}());
|
|
280
333
|
exports.SyncEventResource = SyncEventResource;
|
package/dist/index.d.ts
CHANGED
|
@@ -20,11 +20,11 @@ import { IMachineExectionEngine } from './MachineExecutionEngine/interface';
|
|
|
20
20
|
import { ExecuteMachineInput, ExecuteMachineOutput } from './MachineExecutionEngine/types';
|
|
21
21
|
import { SimpleMachineMemory } from './MachineMemory/Simple';
|
|
22
22
|
import { TelemetredSimpleMachineMemory } from './MachineMemory/TelemetredSimple';
|
|
23
|
-
import { IMachineMemory } from './MachineMemory/interface';
|
|
23
|
+
import { IMachineMemory, MachineMemoryMetadata } from './MachineMemory/interface';
|
|
24
24
|
import { MachineRegistry } from './MachineRegistry';
|
|
25
25
|
import { IMachineRegistry } from './MachineRegistry/interface';
|
|
26
26
|
import { ConfigViolation, ContractViolation, ExecutionViolation } from './errors';
|
|
27
|
-
import { ArvoEventHandlerOpenTelemetryOptions, ArvoEventHandlerOtelSpanOptions, EventHandlerFactory, PartialExcept } from './types';
|
|
27
|
+
import { ArvoEventHandlerOpenTelemetryOptions, ArvoEventHandlerOtelSpanOptions, EventHandlerFactory, Materializable, Materialized, PartialExcept } from './types';
|
|
28
28
|
import { coalesce, coalesceOrDefault, getValueOrDefault, isNullOrUndefined } from './utils';
|
|
29
29
|
import { SimpleEventBroker } from './utils/SimpleEventBroker';
|
|
30
30
|
import { createSimpleEventBroker } from './utils/SimpleEventBroker/helper';
|
|
@@ -34,4 +34,4 @@ declare const xstate: {
|
|
|
34
34
|
emit: typeof emit;
|
|
35
35
|
assign: typeof assign;
|
|
36
36
|
};
|
|
37
|
-
export { ArvoEventHandler, createArvoEventHandler, IArvoEventHandler, ArvoEventHandlerFunctionOutput, ArvoEventHandlerFunctionInput, ArvoEventHandlerFunction, PartialExcept, isNullOrUndefined, getValueOrDefault, coalesce, coalesceOrDefault, ArvoEventHandlerOpenTelemetryOptions, EventHandlerFactory, ContractViolation, ConfigViolation, ExecutionViolation, ArvoMachine, setupArvoMachine, ArvoMachineContext, EnqueueArvoEventActionParam, IMachineRegistry, MachineRegistry, MachineExecutionEngine, IMachineExectionEngine, ExecuteMachineInput, ExecuteMachineOutput, IMachineMemory, SimpleMachineMemory, MachineMemoryRecord, ArvoOrchestratorParam, TransactionViolation, TransactionViolationCause, ArvoOrchestrator, createArvoOrchestrator, SimpleEventBroker, createSimpleEventBroker, TelemetredSimpleMachineMemory, xstate, ArvoResumable, createArvoResumable, ArvoResumableHandler, ArvoResumableState, ArvoDomain, resolveEventDomain, isTransactionViolationError, OrchestrationExecutionStatus, ArvoEventHandlerOtelSpanOptions, runArvoTestSuites, ArvoTestStep, ArvoTestCase, ArvoTestConfig, ArvoTestSuite, ArvoTestResult, IArvoTestFramework, };
|
|
37
|
+
export { ArvoEventHandler, createArvoEventHandler, IArvoEventHandler, ArvoEventHandlerFunctionOutput, ArvoEventHandlerFunctionInput, ArvoEventHandlerFunction, PartialExcept, isNullOrUndefined, getValueOrDefault, coalesce, coalesceOrDefault, ArvoEventHandlerOpenTelemetryOptions, EventHandlerFactory, ContractViolation, ConfigViolation, ExecutionViolation, ArvoMachine, setupArvoMachine, ArvoMachineContext, EnqueueArvoEventActionParam, IMachineRegistry, MachineRegistry, MachineExecutionEngine, IMachineExectionEngine, ExecuteMachineInput, ExecuteMachineOutput, IMachineMemory, SimpleMachineMemory, MachineMemoryRecord, ArvoOrchestratorParam, TransactionViolation, TransactionViolationCause, ArvoOrchestrator, createArvoOrchestrator, SimpleEventBroker, createSimpleEventBroker, TelemetredSimpleMachineMemory, xstate, ArvoResumable, createArvoResumable, ArvoResumableHandler, ArvoResumableState, ArvoDomain, resolveEventDomain, isTransactionViolationError, OrchestrationExecutionStatus, ArvoEventHandlerOtelSpanOptions, runArvoTestSuites, ArvoTestStep, ArvoTestCase, ArvoTestConfig, ArvoTestSuite, ArvoTestResult, IArvoTestFramework, Materializable, Materialized, MachineMemoryMetadata, };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.runArvoTestSuites = exports.OrchestrationExecutionStatus = exports.isTransactionViolationError = exports.resolveEventDomain = exports.ArvoDomain = exports.createArvoResumable = exports.ArvoResumable = exports.xstate = exports.TelemetredSimpleMachineMemory = exports.createSimpleEventBroker = exports.SimpleEventBroker = exports.createArvoOrchestrator = exports.ArvoOrchestrator = exports.TransactionViolationCause = exports.TransactionViolation = exports.SimpleMachineMemory = exports.MachineExecutionEngine = exports.MachineRegistry = exports.setupArvoMachine = exports.ArvoMachine = exports.ExecutionViolation = exports.ConfigViolation = exports.ContractViolation = exports.coalesceOrDefault = exports.coalesce = exports.getValueOrDefault = exports.isNullOrUndefined = exports.createArvoEventHandler = exports.ArvoEventHandler = void 0;
|
|
6
|
+
exports.Materialized = exports.runArvoTestSuites = exports.OrchestrationExecutionStatus = exports.isTransactionViolationError = exports.resolveEventDomain = exports.ArvoDomain = exports.createArvoResumable = exports.ArvoResumable = exports.xstate = exports.TelemetredSimpleMachineMemory = exports.createSimpleEventBroker = exports.SimpleEventBroker = exports.createArvoOrchestrator = exports.ArvoOrchestrator = exports.TransactionViolationCause = exports.TransactionViolation = exports.SimpleMachineMemory = exports.MachineExecutionEngine = exports.MachineRegistry = exports.setupArvoMachine = exports.ArvoMachine = exports.ExecutionViolation = exports.ConfigViolation = exports.ContractViolation = exports.coalesceOrDefault = exports.coalesce = exports.getValueOrDefault = exports.isNullOrUndefined = exports.createArvoEventHandler = exports.ArvoEventHandler = void 0;
|
|
7
7
|
var xstate_1 = require("xstate");
|
|
8
8
|
var ArvoDomain_1 = require("./ArvoDomain");
|
|
9
9
|
Object.defineProperty(exports, "ArvoDomain", { enumerable: true, get: function () { return ArvoDomain_1.ArvoDomain; } });
|
|
@@ -42,6 +42,8 @@ var errors_1 = require("./errors");
|
|
|
42
42
|
Object.defineProperty(exports, "ConfigViolation", { enumerable: true, get: function () { return errors_1.ConfigViolation; } });
|
|
43
43
|
Object.defineProperty(exports, "ContractViolation", { enumerable: true, get: function () { return errors_1.ContractViolation; } });
|
|
44
44
|
Object.defineProperty(exports, "ExecutionViolation", { enumerable: true, get: function () { return errors_1.ExecutionViolation; } });
|
|
45
|
+
var types_1 = require("./types");
|
|
46
|
+
Object.defineProperty(exports, "Materialized", { enumerable: true, get: function () { return types_1.Materialized; } });
|
|
45
47
|
var utils_1 = require("./utils");
|
|
46
48
|
Object.defineProperty(exports, "coalesce", { enumerable: true, get: function () { return utils_1.coalesce; } });
|
|
47
49
|
Object.defineProperty(exports, "coalesceOrDefault", { enumerable: true, get: function () { return utils_1.coalesceOrDefault; } });
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,46 @@ import type { SpanOptions } from '@opentelemetry/api';
|
|
|
2
2
|
import type { ArvoEvent } from 'arvo-core';
|
|
3
3
|
import type IArvoEventHandler from './IArvoEventHandler';
|
|
4
4
|
export type NonEmptyArray<T> = [T, ...T[]];
|
|
5
|
+
/**
|
|
6
|
+
* Represents a value that may or may not have been materialized yet.
|
|
7
|
+
*
|
|
8
|
+
* This type models the distinction between a value whose information is still pending
|
|
9
|
+
* and one that has been fully materialized with its concrete value. The materialized
|
|
10
|
+
* value itself can be of any type, including null or undefined.
|
|
11
|
+
*
|
|
12
|
+
* @template T The type of the value once materialized
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Use this type when you need to explicitly track whether information has been
|
|
16
|
+
* acquired or computed, distinguishing between "we haven't obtained this yet"
|
|
17
|
+
* and "we have obtained this, and here is the value".
|
|
18
|
+
*
|
|
19
|
+
* The 'pending' state indicates materialization has not yet occurred.
|
|
20
|
+
* The 'materialized' state indicates the value has been obtained and is available.
|
|
21
|
+
*
|
|
22
|
+
* This is distinct from optional or nullable types, which represent whether a value
|
|
23
|
+
* exists, rather than whether the information about that value has been determined.
|
|
24
|
+
*/
|
|
25
|
+
export type Materializable<T> = {
|
|
26
|
+
state: 'pending';
|
|
27
|
+
} | {
|
|
28
|
+
state: 'resolved';
|
|
29
|
+
value: T;
|
|
30
|
+
};
|
|
31
|
+
export declare const Materialized: {
|
|
32
|
+
pending: <T>() => Extract<Materializable<T>, {
|
|
33
|
+
state: "pending";
|
|
34
|
+
}>;
|
|
35
|
+
resolved: <T>(value: T) => Extract<Materializable<T>, {
|
|
36
|
+
state: "resolved";
|
|
37
|
+
}>;
|
|
38
|
+
isPending: <T extends Materializable<any>>(value: T) => value is Extract<T, {
|
|
39
|
+
state: "pending";
|
|
40
|
+
}>;
|
|
41
|
+
isResolved: <T extends Materializable<any>>(value: T) => value is Extract<T, {
|
|
42
|
+
state: "resolved";
|
|
43
|
+
}>;
|
|
44
|
+
};
|
|
5
45
|
/**
|
|
6
46
|
* Makes properties optional except specified keys
|
|
7
47
|
*
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Materialized = void 0;
|
|
4
|
+
exports.Materialized = {
|
|
5
|
+
pending: function () { return ({ state: 'pending' }); },
|
|
6
|
+
resolved: function (value) { return ({ state: 'resolved', value: value }); },
|
|
7
|
+
isPending: function (value) {
|
|
8
|
+
return value.state === 'pending';
|
|
9
|
+
},
|
|
10
|
+
isResolved: function (value) {
|
|
11
|
+
return value.state === 'resolved';
|
|
12
|
+
},
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arvo-event-handler",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.27",
|
|
4
4
|
"description": "A complete set of orthogonal event handler and orchestration primitives for Arvo based applications, featuring declarative state machines (XState), imperative resumables for agentic workflows, contract-based routing, OpenTelemetry observability, and in-memory event broker for building composable event-driven architectures.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@opentelemetry/api": "1.9.0",
|
|
69
|
-
"arvo-core": "3.0.
|
|
69
|
+
"arvo-core": "3.0.27",
|
|
70
70
|
"uuid": "11.1.0",
|
|
71
71
|
"xstate": "5.25.0",
|
|
72
72
|
"zod": "3.25.76",
|