@gravito/flux 1.0.0-beta.2 → 1.0.0-beta.3
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/README.zh-TW.md +189 -0
- package/assets/flux-branching.svg +84 -0
- package/dist/bun.cjs +7 -0
- package/dist/bun.cjs.map +1 -0
- package/dist/{storage/BunSQLiteStorage.d.ts → bun.d.cts} +8 -5
- package/dist/bun.d.ts +72 -5
- package/dist/bun.js +2 -2
- package/dist/bun.js.map +1 -0
- package/dist/chunk-J37UUMLM.js +858 -0
- package/dist/chunk-J37UUMLM.js.map +1 -0
- package/dist/chunk-RPECIW7O.cjs +858 -0
- package/dist/chunk-RPECIW7O.cjs.map +1 -0
- package/dist/chunk-SJSPR4ZU.cjs +173 -0
- package/dist/chunk-SJSPR4ZU.cjs.map +1 -0
- package/dist/{chunk-qjdtqchy.js → chunk-ZAMVC732.js} +35 -7
- package/dist/chunk-ZAMVC732.js.map +1 -0
- package/dist/index.cjs +121 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +43 -0
- package/dist/index.d.ts +40 -35
- package/dist/index.js +102 -460
- package/dist/index.js.map +1 -0
- package/dist/index.node.cjs +28 -0
- package/dist/index.node.cjs.map +1 -0
- package/dist/index.node.d.cts +499 -0
- package/dist/index.node.d.ts +494 -13
- package/dist/index.node.js +28 -0
- package/dist/index.node.js.map +1 -0
- package/dist/{types.d.ts → types-DvVHBmP6.d.cts} +59 -18
- package/dist/types-DvVHBmP6.d.ts +235 -0
- package/package.json +26 -22
- package/dist/builder/WorkflowBuilder.d.ts +0 -96
- package/dist/builder/WorkflowBuilder.d.ts.map +0 -1
- package/dist/builder/index.d.ts +0 -2
- package/dist/builder/index.d.ts.map +0 -1
- package/dist/bun.d.ts.map +0 -1
- package/dist/core/ContextManager.d.ts +0 -40
- package/dist/core/ContextManager.d.ts.map +0 -1
- package/dist/core/StateMachine.d.ts +0 -43
- package/dist/core/StateMachine.d.ts.map +0 -1
- package/dist/core/StepExecutor.d.ts +0 -34
- package/dist/core/StepExecutor.d.ts.map +0 -1
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/engine/FluxEngine.d.ts +0 -66
- package/dist/engine/FluxEngine.d.ts.map +0 -1
- package/dist/engine/index.d.ts +0 -2
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.node.d.ts.map +0 -1
- package/dist/logger/FluxLogger.d.ts +0 -40
- package/dist/logger/FluxLogger.d.ts.map +0 -1
- package/dist/logger/index.d.ts +0 -2
- package/dist/logger/index.d.ts.map +0 -1
- package/dist/node/index.mjs +0 -619
- package/dist/orbit/OrbitFlux.d.ts +0 -107
- package/dist/orbit/OrbitFlux.d.ts.map +0 -1
- package/dist/orbit/index.d.ts +0 -2
- package/dist/orbit/index.d.ts.map +0 -1
- package/dist/storage/BunSQLiteStorage.d.ts.map +0 -1
- package/dist/storage/MemoryStorage.d.ts +0 -28
- package/dist/storage/MemoryStorage.d.ts.map +0 -1
- package/dist/storage/index.d.ts +0 -3
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,38 +1,43 @@
|
|
|
1
|
+
import { FluxEngine } from './index.node.js';
|
|
2
|
+
export { ContextManager, FluxConsoleLogger, FluxSilentLogger, JsonFileTraceSink, MemoryStorage, OrbitFlux, OrbitFluxOptions, StateMachine, StepExecutor, WorkflowBuilder, createWorkflow } from './index.node.js';
|
|
3
|
+
import { W as WorkflowDefinition } from './types-DvVHBmP6.js';
|
|
4
|
+
export { F as FluxConfig, a as FluxLogger, b as FluxResult, c as FluxTraceEvent, d as FluxTraceEventType, e as FluxTraceSink, S as StepDefinition, f as StepDescriptor, g as StepExecution, h as StepResult, i as WorkflowContext, j as WorkflowDescriptor, k as WorkflowFilter, l as WorkflowState, m as WorkflowStatus, n as WorkflowStorage } from './types-DvVHBmP6.js';
|
|
5
|
+
export { BunSQLiteStorage, BunSQLiteStorageOptions } from './bun.js';
|
|
6
|
+
import 'bun:sqlite';
|
|
7
|
+
|
|
8
|
+
interface ProfileMetrics {
|
|
9
|
+
durationMs: number;
|
|
10
|
+
cpuUserMs: number;
|
|
11
|
+
cpuSysMs: number;
|
|
12
|
+
memDeltaBytes: number;
|
|
13
|
+
cpuRatio: number;
|
|
14
|
+
}
|
|
15
|
+
interface ProfileRecommendation {
|
|
16
|
+
type: 'IO_BOUND' | 'CPU_BOUND' | 'MEMORY_BOUND';
|
|
17
|
+
safeConcurrency: number;
|
|
18
|
+
efficientConcurrency: number;
|
|
19
|
+
suggestedConcurrency: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
1
22
|
/**
|
|
2
|
-
*
|
|
23
|
+
* Workflow Profiler
|
|
3
24
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @example Basic Usage
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import { FluxEngine, createWorkflow } from '@gravito/flux'
|
|
9
|
-
*
|
|
10
|
-
* const workflow = createWorkflow('order-process')
|
|
11
|
-
* .input<{ orderId: string }>()
|
|
12
|
-
* .step('validate', async (ctx) => {
|
|
13
|
-
* ctx.data.order = await fetchOrder(ctx.input.orderId)
|
|
14
|
-
* })
|
|
15
|
-
* .step('process', async (ctx) => {
|
|
16
|
-
* await processPayment(ctx.data.order)
|
|
17
|
-
* })
|
|
18
|
-
* .commit('notify', async (ctx) => {
|
|
19
|
-
* await sendEmail(ctx.data.order.email)
|
|
20
|
-
* })
|
|
21
|
-
*
|
|
22
|
-
* const engine = new FluxEngine()
|
|
23
|
-
* const result = await engine.execute(workflow, { orderId: '123' })
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* @module @gravito/flux
|
|
25
|
+
* Analyzes workflow performance characteristics to recommend
|
|
26
|
+
* optimal concurrency settings for Consumer workers.
|
|
27
27
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
declare class WorkflowProfiler {
|
|
29
|
+
private engine?;
|
|
30
|
+
constructor(engine?: FluxEngine | undefined);
|
|
31
|
+
/**
|
|
32
|
+
* Run a profile session for a specific workflow
|
|
33
|
+
*/
|
|
34
|
+
profile<TInput>(workflow: WorkflowDefinition<TInput, any>, input: TInput): Promise<ProfileMetrics>;
|
|
35
|
+
/**
|
|
36
|
+
* Generate recommendations based on metrics and current environment
|
|
37
|
+
*/
|
|
38
|
+
recommend(metrics: ProfileMetrics, config?: {
|
|
39
|
+
configuredConcurrency?: number;
|
|
40
|
+
}): ProfileRecommendation;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { FluxEngine, type ProfileMetrics, type ProfileRecommendation, WorkflowDefinition, WorkflowProfiler };
|
package/dist/index.js
CHANGED
|
@@ -1,479 +1,121 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
ContextManager,
|
|
3
|
+
FluxConsoleLogger,
|
|
4
|
+
FluxEngine,
|
|
5
|
+
FluxSilentLogger,
|
|
6
|
+
JsonFileTraceSink,
|
|
7
|
+
MemoryStorage,
|
|
8
|
+
OrbitFlux,
|
|
9
|
+
StateMachine,
|
|
10
|
+
StepExecutor,
|
|
11
|
+
WorkflowBuilder,
|
|
12
|
+
createWorkflow
|
|
13
|
+
} from "./chunk-J37UUMLM.js";
|
|
2
14
|
import {
|
|
3
15
|
BunSQLiteStorage
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
|
|
6
|
-
// src/builder/WorkflowBuilder.ts
|
|
7
|
-
class WorkflowBuilder {
|
|
8
|
-
_name;
|
|
9
|
-
_steps = [];
|
|
10
|
-
_validateInput;
|
|
11
|
-
constructor(name) {
|
|
12
|
-
this._name = name;
|
|
13
|
-
}
|
|
14
|
-
input() {
|
|
15
|
-
return this;
|
|
16
|
-
}
|
|
17
|
-
validate(validator) {
|
|
18
|
-
this._validateInput = validator;
|
|
19
|
-
return this;
|
|
20
|
-
}
|
|
21
|
-
step(name, handler, options) {
|
|
22
|
-
this._steps.push({
|
|
23
|
-
name,
|
|
24
|
-
handler,
|
|
25
|
-
retries: options?.retries,
|
|
26
|
-
timeout: options?.timeout,
|
|
27
|
-
when: options?.when,
|
|
28
|
-
commit: false
|
|
29
|
-
});
|
|
30
|
-
return this;
|
|
31
|
-
}
|
|
32
|
-
commit(name, handler, options) {
|
|
33
|
-
this._steps.push({
|
|
34
|
-
name,
|
|
35
|
-
handler,
|
|
36
|
-
retries: options?.retries,
|
|
37
|
-
timeout: options?.timeout,
|
|
38
|
-
when: options?.when,
|
|
39
|
-
commit: true
|
|
40
|
-
});
|
|
41
|
-
return this;
|
|
42
|
-
}
|
|
43
|
-
build() {
|
|
44
|
-
if (this._steps.length === 0) {
|
|
45
|
-
throw new Error(`Workflow "${this._name}" has no steps`);
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
name: this._name,
|
|
49
|
-
steps: [...this._steps],
|
|
50
|
-
validateInput: this._validateInput
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
get name() {
|
|
54
|
-
return this._name;
|
|
55
|
-
}
|
|
56
|
-
get stepCount() {
|
|
57
|
-
return this._steps.length;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function createWorkflow(name) {
|
|
61
|
-
return new WorkflowBuilder(name);
|
|
62
|
-
}
|
|
63
|
-
// src/core/ContextManager.ts
|
|
64
|
-
function generateId() {
|
|
65
|
-
return crypto.randomUUID();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
class ContextManager {
|
|
69
|
-
create(name, input, stepCount) {
|
|
70
|
-
const history = Array.from({ length: stepCount }, (_, _i) => ({
|
|
71
|
-
name: "",
|
|
72
|
-
status: "pending",
|
|
73
|
-
retries: 0
|
|
74
|
-
}));
|
|
75
|
-
return {
|
|
76
|
-
id: generateId(),
|
|
77
|
-
name,
|
|
78
|
-
input,
|
|
79
|
-
data: {},
|
|
80
|
-
status: "pending",
|
|
81
|
-
currentStep: 0,
|
|
82
|
-
history
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
restore(state) {
|
|
86
|
-
return {
|
|
87
|
-
id: state.id,
|
|
88
|
-
name: state.name,
|
|
89
|
-
input: state.input,
|
|
90
|
-
data: { ...state.data },
|
|
91
|
-
status: state.status,
|
|
92
|
-
currentStep: state.currentStep,
|
|
93
|
-
history: state.history.map((h) => ({ ...h }))
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
toState(ctx) {
|
|
97
|
-
return {
|
|
98
|
-
id: ctx.id,
|
|
99
|
-
name: ctx.name,
|
|
100
|
-
status: ctx.status,
|
|
101
|
-
input: ctx.input,
|
|
102
|
-
data: { ...ctx.data },
|
|
103
|
-
currentStep: ctx.currentStep,
|
|
104
|
-
history: ctx.history.map((h) => ({ ...h })),
|
|
105
|
-
createdAt: new Date,
|
|
106
|
-
updatedAt: new Date
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
updateStatus(ctx, status) {
|
|
110
|
-
return {
|
|
111
|
-
...ctx,
|
|
112
|
-
status
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
advanceStep(ctx) {
|
|
116
|
-
return {
|
|
117
|
-
...ctx,
|
|
118
|
-
currentStep: ctx.currentStep + 1
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
setStepName(ctx, index, name) {
|
|
122
|
-
if (ctx.history[index]) {
|
|
123
|
-
ctx.history[index].name = name;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// src/core/StateMachine.ts
|
|
128
|
-
var TRANSITIONS = {
|
|
129
|
-
pending: ["running", "failed"],
|
|
130
|
-
running: ["paused", "completed", "failed"],
|
|
131
|
-
paused: ["running", "failed"],
|
|
132
|
-
completed: [],
|
|
133
|
-
failed: ["pending"]
|
|
134
|
-
};
|
|
16
|
+
} from "./chunk-ZAMVC732.js";
|
|
135
17
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (!this.canTransition(to)) {
|
|
146
|
-
throw new Error(`Invalid state transition: ${this._status} \u2192 ${to}`);
|
|
18
|
+
// src/profiler/WorkflowProfiler.ts
|
|
19
|
+
import * as os from "os";
|
|
20
|
+
var WorkflowProfiler = class {
|
|
21
|
+
constructor(engine) {
|
|
22
|
+
this.engine = engine;
|
|
23
|
+
if (!this.engine) {
|
|
24
|
+
this.engine = new FluxEngine({
|
|
25
|
+
logger: new FluxSilentLogger()
|
|
26
|
+
});
|
|
147
27
|
}
|
|
148
|
-
const from = this._status;
|
|
149
|
-
this._status = to;
|
|
150
|
-
this.dispatchEvent(new CustomEvent("transition", {
|
|
151
|
-
detail: { from, to }
|
|
152
|
-
}));
|
|
153
|
-
}
|
|
154
|
-
forceStatus(status) {
|
|
155
|
-
this._status = status;
|
|
156
|
-
}
|
|
157
|
-
isTerminal() {
|
|
158
|
-
return this._status === "completed" || this._status === "failed";
|
|
159
28
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
defaultTimeout;
|
|
168
|
-
constructor(options = {}) {
|
|
169
|
-
this.defaultRetries = options.defaultRetries ?? 3;
|
|
170
|
-
this.defaultTimeout = options.defaultTimeout ?? 30000;
|
|
171
|
-
}
|
|
172
|
-
async execute(step, ctx, execution) {
|
|
173
|
-
const maxRetries = step.retries ?? this.defaultRetries;
|
|
174
|
-
const timeout = step.timeout ?? this.defaultTimeout;
|
|
175
|
-
const startTime = Date.now();
|
|
176
|
-
if (step.when && !step.when(ctx)) {
|
|
177
|
-
execution.status = "skipped";
|
|
178
|
-
return {
|
|
179
|
-
success: true,
|
|
180
|
-
duration: 0
|
|
181
|
-
};
|
|
29
|
+
/**
|
|
30
|
+
* Run a profile session for a specific workflow
|
|
31
|
+
*/
|
|
32
|
+
async profile(workflow, input) {
|
|
33
|
+
try {
|
|
34
|
+
await this.engine.execute(workflow, input);
|
|
35
|
+
} catch {
|
|
182
36
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
let lastError;
|
|
186
|
-
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
187
|
-
execution.retries = attempt;
|
|
188
|
-
try {
|
|
189
|
-
await this.executeWithTimeout(step.handler, ctx, timeout);
|
|
190
|
-
execution.status = "completed";
|
|
191
|
-
execution.completedAt = new Date;
|
|
192
|
-
execution.duration = Date.now() - startTime;
|
|
193
|
-
return {
|
|
194
|
-
success: true,
|
|
195
|
-
duration: execution.duration
|
|
196
|
-
};
|
|
197
|
-
} catch (error) {
|
|
198
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
199
|
-
if (attempt < maxRetries) {
|
|
200
|
-
await this.sleep(Math.min(1000 * 2 ** attempt, 1e4));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
37
|
+
if (global.gc) {
|
|
38
|
+
global.gc();
|
|
203
39
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
40
|
+
const startCpu = process.cpuUsage();
|
|
41
|
+
const startMem = process.memoryUsage().heapUsed;
|
|
42
|
+
const startTime = process.hrtime.bigint();
|
|
43
|
+
await this.engine.execute(workflow, input);
|
|
44
|
+
const endTime = process.hrtime.bigint();
|
|
45
|
+
const endCpu = process.cpuUsage(startCpu);
|
|
46
|
+
const endMem = process.memoryUsage().heapUsed;
|
|
47
|
+
const durationNs = Number(endTime - startTime);
|
|
48
|
+
const durationMs = durationNs / 1e6;
|
|
49
|
+
const cpuUserMs = endCpu.user / 1e3;
|
|
50
|
+
const cpuSysMs = endCpu.system / 1e3;
|
|
51
|
+
const totalCpuMs = cpuUserMs + cpuSysMs;
|
|
52
|
+
const memDeltaBytes = Math.max(0, endMem - startMem);
|
|
53
|
+
const cpuRatio = totalCpuMs / durationMs;
|
|
208
54
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
55
|
+
durationMs,
|
|
56
|
+
cpuUserMs,
|
|
57
|
+
cpuSysMs,
|
|
58
|
+
memDeltaBytes,
|
|
59
|
+
cpuRatio
|
|
212
60
|
};
|
|
213
61
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
class MemoryStorage {
|
|
226
|
-
store = new Map;
|
|
227
|
-
async save(state) {
|
|
228
|
-
this.store.set(state.id, {
|
|
229
|
-
...state,
|
|
230
|
-
updatedAt: new Date
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
async load(id) {
|
|
234
|
-
return this.store.get(id) ?? null;
|
|
235
|
-
}
|
|
236
|
-
async list(filter) {
|
|
237
|
-
let results = Array.from(this.store.values());
|
|
238
|
-
if (filter?.name) {
|
|
239
|
-
results = results.filter((s) => s.name === filter.name);
|
|
240
|
-
}
|
|
241
|
-
if (filter?.status) {
|
|
242
|
-
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
243
|
-
results = results.filter((s) => statuses.includes(s.status));
|
|
244
|
-
}
|
|
245
|
-
results.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
246
|
-
if (filter?.offset) {
|
|
247
|
-
results = results.slice(filter.offset);
|
|
248
|
-
}
|
|
249
|
-
if (filter?.limit) {
|
|
250
|
-
results = results.slice(0, filter.limit);
|
|
251
|
-
}
|
|
252
|
-
return results;
|
|
253
|
-
}
|
|
254
|
-
async delete(id) {
|
|
255
|
-
this.store.delete(id);
|
|
256
|
-
}
|
|
257
|
-
async init() {}
|
|
258
|
-
async close() {
|
|
259
|
-
this.store.clear();
|
|
260
|
-
}
|
|
261
|
-
size() {
|
|
262
|
-
return this.store.size;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// src/engine/FluxEngine.ts
|
|
267
|
-
class FluxEngine {
|
|
268
|
-
storage;
|
|
269
|
-
executor;
|
|
270
|
-
contextManager;
|
|
271
|
-
config;
|
|
272
|
-
constructor(config = {}) {
|
|
273
|
-
this.config = config;
|
|
274
|
-
this.storage = config.storage ?? new MemoryStorage;
|
|
275
|
-
this.executor = new StepExecutor({
|
|
276
|
-
defaultRetries: config.defaultRetries,
|
|
277
|
-
defaultTimeout: config.defaultTimeout
|
|
278
|
-
});
|
|
279
|
-
this.contextManager = new ContextManager;
|
|
280
|
-
}
|
|
281
|
-
async execute(workflow, input) {
|
|
282
|
-
const startTime = Date.now();
|
|
283
|
-
const definition = workflow instanceof WorkflowBuilder ? workflow.build() : workflow;
|
|
284
|
-
if (definition.validateInput && !definition.validateInput(input)) {
|
|
285
|
-
throw new Error(`Invalid input for workflow "${definition.name}"`);
|
|
286
|
-
}
|
|
287
|
-
const ctx = this.contextManager.create(definition.name, input, definition.steps.length);
|
|
288
|
-
const stateMachine = new StateMachine;
|
|
289
|
-
await this.storage.save(this.contextManager.toState(ctx));
|
|
290
|
-
try {
|
|
291
|
-
stateMachine.transition("running");
|
|
292
|
-
Object.assign(ctx, { status: "running" });
|
|
293
|
-
for (let i = 0;i < definition.steps.length; i++) {
|
|
294
|
-
const step = definition.steps[i];
|
|
295
|
-
const execution = ctx.history[i];
|
|
296
|
-
this.contextManager.setStepName(ctx, i, step.name);
|
|
297
|
-
Object.assign(ctx, { currentStep: i });
|
|
298
|
-
this.config.on?.stepStart?.(step.name, ctx);
|
|
299
|
-
const result = await this.executor.execute(step, ctx, execution);
|
|
300
|
-
if (result.success) {
|
|
301
|
-
this.config.on?.stepComplete?.(step.name, ctx, result);
|
|
302
|
-
} else {
|
|
303
|
-
this.config.on?.stepError?.(step.name, ctx, result.error);
|
|
304
|
-
stateMachine.transition("failed");
|
|
305
|
-
Object.assign(ctx, { status: "failed" });
|
|
306
|
-
await this.storage.save({
|
|
307
|
-
...this.contextManager.toState(ctx),
|
|
308
|
-
error: result.error?.message
|
|
309
|
-
});
|
|
310
|
-
return {
|
|
311
|
-
id: ctx.id,
|
|
312
|
-
status: "failed",
|
|
313
|
-
data: ctx.data,
|
|
314
|
-
history: ctx.history,
|
|
315
|
-
duration: Date.now() - startTime,
|
|
316
|
-
error: result.error
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
await this.storage.save(this.contextManager.toState(ctx));
|
|
320
|
-
}
|
|
321
|
-
stateMachine.transition("completed");
|
|
322
|
-
Object.assign(ctx, { status: "completed" });
|
|
323
|
-
await this.storage.save({
|
|
324
|
-
...this.contextManager.toState(ctx),
|
|
325
|
-
completedAt: new Date
|
|
326
|
-
});
|
|
327
|
-
this.config.on?.workflowComplete?.(ctx);
|
|
328
|
-
return {
|
|
329
|
-
id: ctx.id,
|
|
330
|
-
status: "completed",
|
|
331
|
-
data: ctx.data,
|
|
332
|
-
history: ctx.history,
|
|
333
|
-
duration: Date.now() - startTime
|
|
334
|
-
};
|
|
335
|
-
} catch (error) {
|
|
336
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
337
|
-
this.config.on?.workflowError?.(ctx, err);
|
|
338
|
-
stateMachine.forceStatus("failed");
|
|
339
|
-
Object.assign(ctx, { status: "failed" });
|
|
340
|
-
await this.storage.save({
|
|
341
|
-
...this.contextManager.toState(ctx),
|
|
342
|
-
error: err.message
|
|
343
|
-
});
|
|
344
|
-
return {
|
|
345
|
-
id: ctx.id,
|
|
346
|
-
status: "failed",
|
|
347
|
-
data: ctx.data,
|
|
348
|
-
history: ctx.history,
|
|
349
|
-
duration: Date.now() - startTime,
|
|
350
|
-
error: err
|
|
351
|
-
};
|
|
62
|
+
/**
|
|
63
|
+
* Generate recommendations based on metrics and current environment
|
|
64
|
+
*/
|
|
65
|
+
recommend(metrics, config) {
|
|
66
|
+
const totalMem = os.totalmem();
|
|
67
|
+
const cpus2 = os.cpus().length;
|
|
68
|
+
let type = "IO_BOUND";
|
|
69
|
+
if (metrics.cpuRatio > 0.5) {
|
|
70
|
+
type = "CPU_BOUND";
|
|
71
|
+
} else if (metrics.memDeltaBytes > 50 * 1024 * 1024) {
|
|
72
|
+
type = "MEMORY_BOUND";
|
|
352
73
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
74
|
+
const safeMem = totalMem * 0.7;
|
|
75
|
+
const perInstanceMem = Math.max(metrics.memDeltaBytes, 1024 * 1024);
|
|
76
|
+
const maxMemConcurrency = Math.floor(safeMem / perInstanceMem);
|
|
77
|
+
const cpuEfficiencyFactor = 1 / Math.max(metrics.cpuRatio, 1e-3);
|
|
78
|
+
const maxCpuConcurrency = Math.floor(cpus2 * cpuEfficiencyFactor);
|
|
79
|
+
const safe = Math.min(maxMemConcurrency, 200);
|
|
80
|
+
let efficient = Math.min(maxCpuConcurrency, 200);
|
|
81
|
+
if (type === "CPU_BOUND") {
|
|
82
|
+
efficient = cpus2;
|
|
358
83
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return this.storage.list(filter);
|
|
366
|
-
}
|
|
367
|
-
async init() {
|
|
368
|
-
await this.storage.init?.();
|
|
369
|
-
}
|
|
370
|
-
async close() {
|
|
371
|
-
await this.storage.close?.();
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
// src/logger/FluxLogger.ts
|
|
375
|
-
class FluxConsoleLogger {
|
|
376
|
-
prefix;
|
|
377
|
-
constructor(prefix = "[Flux]") {
|
|
378
|
-
this.prefix = prefix;
|
|
379
|
-
}
|
|
380
|
-
debug(message, ...args) {
|
|
381
|
-
console.debug(`${this.prefix} ${message}`, ...args);
|
|
382
|
-
}
|
|
383
|
-
info(message, ...args) {
|
|
384
|
-
console.info(`${this.prefix} ${message}`, ...args);
|
|
385
|
-
}
|
|
386
|
-
warn(message, ...args) {
|
|
387
|
-
console.warn(`${this.prefix} ${message}`, ...args);
|
|
388
|
-
}
|
|
389
|
-
error(message, ...args) {
|
|
390
|
-
console.error(`${this.prefix} ${message}`, ...args);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
class FluxSilentLogger {
|
|
395
|
-
debug() {}
|
|
396
|
-
info() {}
|
|
397
|
-
warn() {}
|
|
398
|
-
error() {}
|
|
399
|
-
}
|
|
400
|
-
// src/orbit/OrbitFlux.ts
|
|
401
|
-
class OrbitFlux {
|
|
402
|
-
options;
|
|
403
|
-
engine;
|
|
404
|
-
constructor(options = {}) {
|
|
405
|
-
this.options = {
|
|
406
|
-
storage: "memory",
|
|
407
|
-
exposeAs: "flux",
|
|
408
|
-
defaultRetries: 3,
|
|
409
|
-
defaultTimeout: 30000,
|
|
410
|
-
...options
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
static configure(options = {}) {
|
|
414
|
-
return new OrbitFlux(options);
|
|
415
|
-
}
|
|
416
|
-
async install(core) {
|
|
417
|
-
const { storage, dbPath, exposeAs, defaultRetries, defaultTimeout, logger } = this.options;
|
|
418
|
-
let storageAdapter;
|
|
419
|
-
if (typeof storage === "string") {
|
|
420
|
-
switch (storage) {
|
|
421
|
-
case "sqlite":
|
|
422
|
-
storageAdapter = new BunSQLiteStorage({ path: dbPath });
|
|
423
|
-
break;
|
|
424
|
-
default:
|
|
425
|
-
storageAdapter = new MemoryStorage;
|
|
426
|
-
}
|
|
84
|
+
const recommended = Math.min(safe, efficient);
|
|
85
|
+
let reason = "";
|
|
86
|
+
if (type === "IO_BOUND") {
|
|
87
|
+
reason = `Workflow is I/O intensive (CPU usage ${(metrics.cpuRatio * 100).toFixed(1)}%). It is safe to run high concurrency up to ${recommended}.`;
|
|
88
|
+
} else if (type === "CPU_BOUND") {
|
|
89
|
+
reason = `Workflow is CPU intensive. Limiting concurrency to match CPU cores (${cpus2}) is recommended to prevent blocking.`;
|
|
427
90
|
} else {
|
|
428
|
-
|
|
91
|
+
reason = `Workflow consumes significant memory (${(metrics.memDeltaBytes / 1024 / 1024).toFixed(1)}MB). Concurrency limited by available RAM.`;
|
|
429
92
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
},
|
|
441
|
-
on: {
|
|
442
|
-
stepStart: (step) => {
|
|
443
|
-
core.hooks.doAction("flux:step:start", { step });
|
|
444
|
-
},
|
|
445
|
-
stepComplete: (step, ctx, result) => {
|
|
446
|
-
core.hooks.doAction("flux:step:complete", { step, ctx, result });
|
|
447
|
-
},
|
|
448
|
-
stepError: (step, ctx, error) => {
|
|
449
|
-
core.hooks.doAction("flux:step:error", { step, ctx, error });
|
|
450
|
-
},
|
|
451
|
-
workflowComplete: (ctx) => {
|
|
452
|
-
core.hooks.doAction("flux:workflow:complete", { ctx });
|
|
453
|
-
},
|
|
454
|
-
workflowError: (ctx, error) => {
|
|
455
|
-
core.hooks.doAction("flux:workflow:error", { ctx, error });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
93
|
+
if (config?.configuredConcurrency && config.configuredConcurrency > recommended) {
|
|
94
|
+
reason += `
|
|
95
|
+
\u26A0\uFE0F Warning: Your current setting (${config.configuredConcurrency}) exceeds the recommended limit (${recommended}).`;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
type,
|
|
99
|
+
safeConcurrency: safe,
|
|
100
|
+
efficientConcurrency: efficient,
|
|
101
|
+
suggestedConcurrency: `${Math.max(1, Math.floor(recommended * 0.5))} - ${recommended}`,
|
|
102
|
+
reason
|
|
458
103
|
};
|
|
459
|
-
this.engine = new FluxEngine(engineConfig);
|
|
460
|
-
core.services.set(exposeAs, this.engine);
|
|
461
|
-
core.logger.info(`[OrbitFlux] Initialized (Storage: ${typeof storage === "string" ? storage : "custom"})`);
|
|
462
|
-
}
|
|
463
|
-
getEngine() {
|
|
464
|
-
return this.engine;
|
|
465
104
|
}
|
|
466
|
-
}
|
|
105
|
+
};
|
|
467
106
|
export {
|
|
468
|
-
|
|
469
|
-
WorkflowBuilder,
|
|
470
|
-
StepExecutor,
|
|
471
|
-
StateMachine,
|
|
472
|
-
OrbitFlux,
|
|
473
|
-
MemoryStorage,
|
|
474
|
-
FluxSilentLogger,
|
|
475
|
-
FluxEngine,
|
|
476
|
-
FluxConsoleLogger,
|
|
107
|
+
BunSQLiteStorage,
|
|
477
108
|
ContextManager,
|
|
478
|
-
|
|
109
|
+
FluxConsoleLogger,
|
|
110
|
+
FluxEngine,
|
|
111
|
+
FluxSilentLogger,
|
|
112
|
+
JsonFileTraceSink,
|
|
113
|
+
MemoryStorage,
|
|
114
|
+
OrbitFlux,
|
|
115
|
+
StateMachine,
|
|
116
|
+
StepExecutor,
|
|
117
|
+
WorkflowBuilder,
|
|
118
|
+
WorkflowProfiler,
|
|
119
|
+
createWorkflow
|
|
479
120
|
};
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/profiler/WorkflowProfiler.ts"],"sourcesContent":["import * as os from 'os'\nimport { FluxEngine } from '../engine/FluxEngine'\nimport { FluxSilentLogger } from '../logger/FluxLogger'\nimport type { WorkflowDefinition } from '../types'\n\nexport interface ProfileMetrics {\n durationMs: number\n cpuUserMs: number\n cpuSysMs: number\n memDeltaBytes: number\n cpuRatio: number\n}\n\nexport interface ProfileRecommendation {\n type: 'IO_BOUND' | 'CPU_BOUND' | 'MEMORY_BOUND'\n safeConcurrency: number\n efficientConcurrency: number\n suggestedConcurrency: string\n reason: string\n}\n\n/**\n * Workflow Profiler\n *\n * Analyzes workflow performance characteristics to recommend\n * optimal concurrency settings for Consumer workers.\n */\nexport class WorkflowProfiler {\n constructor(private engine?: FluxEngine) {\n if (!this.engine) {\n // Default minimal engine for profiling (Silent)\n this.engine = new FluxEngine({\n logger: new FluxSilentLogger(),\n })\n }\n }\n\n /**\n * Run a profile session for a specific workflow\n */\n async profile<TInput>(\n workflow: WorkflowDefinition<TInput, any>,\n input: TInput\n ): Promise<ProfileMetrics> {\n // 1. Warmup (JIT)\n try {\n await this.engine!.execute(workflow, input)\n } catch {}\n\n // 2. Measure\n if (global.gc) {\n global.gc()\n }\n\n const startCpu = process.cpuUsage()\n const startMem = process.memoryUsage().heapUsed\n const startTime = process.hrtime.bigint()\n\n await this.engine!.execute(workflow, input)\n\n const endTime = process.hrtime.bigint()\n const endCpu = process.cpuUsage(startCpu)\n const endMem = process.memoryUsage().heapUsed\n\n // 3. Calculate\n const durationNs = Number(endTime - startTime)\n const durationMs = durationNs / 1_000_000\n const cpuUserMs = endCpu.user / 1000\n const cpuSysMs = endCpu.system / 1000\n const totalCpuMs = cpuUserMs + cpuSysMs\n const memDeltaBytes = Math.max(0, endMem - startMem) // Clamp to 0\n\n // CPU Ratio: How much % of the time was spent on CPU vs Waiting\n const cpuRatio = totalCpuMs / durationMs\n\n return {\n durationMs,\n cpuUserMs,\n cpuSysMs,\n memDeltaBytes,\n cpuRatio,\n }\n }\n\n /**\n * Generate recommendations based on metrics and current environment\n */\n recommend(\n metrics: ProfileMetrics,\n config?: { configuredConcurrency?: number }\n ): ProfileRecommendation {\n const totalMem = os.totalmem()\n const cpus = os.cpus().length\n\n // 1. Analyze Bottleneck Type\n let type: ProfileRecommendation['type'] = 'IO_BOUND'\n if (metrics.cpuRatio > 0.5) {\n type = 'CPU_BOUND'\n } else if (metrics.memDeltaBytes > 50 * 1024 * 1024) {\n // > 50MB per run\n type = 'MEMORY_BOUND'\n }\n\n // 2. Calculate Limits\n\n // Memory Limit: Keep 30% buffer for system, divide rest by per-workflow memory\n const safeMem = totalMem * 0.7\n // Use at least 1MB as baseline to avoid division by zero or huge numbers\n const perInstanceMem = Math.max(metrics.memDeltaBytes, 1024 * 1024)\n const maxMemConcurrency = Math.floor(safeMem / perInstanceMem)\n\n // CPU Limit:\n // If IO Bound (0.2% cpu), we can run many. 100% / 0.2% = 500 tasks per core.\n // We cap efficiency at a reasonable number to avoid Event Loop Lag density.\n const cpuEfficiencyFactor = 1 / Math.max(metrics.cpuRatio, 0.001) // Avoid div by 0\n const maxCpuConcurrency = Math.floor(cpus * cpuEfficiencyFactor)\n\n // 3. Synthesize Recommendation\n const safe = Math.min(maxMemConcurrency, 200) // Hard cap at 200 for sanity\n let efficient = Math.min(maxCpuConcurrency, 200)\n\n // If CPU bound, strict limit based on cores\n if (type === 'CPU_BOUND') {\n efficient = cpus // 1:1 mapping is best for CPU bound\n }\n\n const recommended = Math.min(safe, efficient)\n\n let reason = ''\n if (type === 'IO_BOUND') {\n reason = `Workflow is I/O intensive (CPU usage ${(metrics.cpuRatio * 100).toFixed(1)}%). It is safe to run high concurrency up to ${recommended}.`\n } else if (type === 'CPU_BOUND') {\n reason = `Workflow is CPU intensive. Limiting concurrency to match CPU cores (${cpus}) is recommended to prevent blocking.`\n } else {\n reason = `Workflow consumes significant memory (${(metrics.memDeltaBytes / 1024 / 1024).toFixed(1)}MB). Concurrency limited by available RAM.`\n }\n\n if (config?.configuredConcurrency && config.configuredConcurrency > recommended) {\n reason += ` \\n⚠️ Warning: Your current setting (${config.configuredConcurrency}) exceeds the recommended limit (${recommended}).`\n }\n\n return {\n type,\n safeConcurrency: safe,\n efficientConcurrency: efficient,\n suggestedConcurrency: `${Math.max(1, Math.floor(recommended * 0.5))} - ${recommended}`,\n reason,\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,YAAY,QAAQ;AA2Bb,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAAoB,QAAqB;AAArB;AAClB,QAAI,CAAC,KAAK,QAAQ;AAEhB,WAAK,SAAS,IAAI,WAAW;AAAA,QAC3B,QAAQ,IAAI,iBAAiB;AAAA,MAC/B,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QACJ,UACA,OACyB;AAEzB,QAAI;AACF,YAAM,KAAK,OAAQ,QAAQ,UAAU,KAAK;AAAA,IAC5C,QAAQ;AAAA,IAAC;AAGT,QAAI,OAAO,IAAI;AACb,aAAO,GAAG;AAAA,IACZ;AAEA,UAAM,WAAW,QAAQ,SAAS;AAClC,UAAM,WAAW,QAAQ,YAAY,EAAE;AACvC,UAAM,YAAY,QAAQ,OAAO,OAAO;AAExC,UAAM,KAAK,OAAQ,QAAQ,UAAU,KAAK;AAE1C,UAAM,UAAU,QAAQ,OAAO,OAAO;AACtC,UAAM,SAAS,QAAQ,SAAS,QAAQ;AACxC,UAAM,SAAS,QAAQ,YAAY,EAAE;AAGrC,UAAM,aAAa,OAAO,UAAU,SAAS;AAC7C,UAAM,aAAa,aAAa;AAChC,UAAM,YAAY,OAAO,OAAO;AAChC,UAAM,WAAW,OAAO,SAAS;AACjC,UAAM,aAAa,YAAY;AAC/B,UAAM,gBAAgB,KAAK,IAAI,GAAG,SAAS,QAAQ;AAGnD,UAAM,WAAW,aAAa;AAE9B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UACE,SACA,QACuB;AACvB,UAAM,WAAc,YAAS;AAC7B,UAAMA,QAAU,QAAK,EAAE;AAGvB,QAAI,OAAsC;AAC1C,QAAI,QAAQ,WAAW,KAAK;AAC1B,aAAO;AAAA,IACT,WAAW,QAAQ,gBAAgB,KAAK,OAAO,MAAM;AAEnD,aAAO;AAAA,IACT;AAKA,UAAM,UAAU,WAAW;AAE3B,UAAM,iBAAiB,KAAK,IAAI,QAAQ,eAAe,OAAO,IAAI;AAClE,UAAM,oBAAoB,KAAK,MAAM,UAAU,cAAc;AAK7D,UAAM,sBAAsB,IAAI,KAAK,IAAI,QAAQ,UAAU,IAAK;AAChE,UAAM,oBAAoB,KAAK,MAAMA,QAAO,mBAAmB;AAG/D,UAAM,OAAO,KAAK,IAAI,mBAAmB,GAAG;AAC5C,QAAI,YAAY,KAAK,IAAI,mBAAmB,GAAG;AAG/C,QAAI,SAAS,aAAa;AACxB,kBAAYA;AAAA,IACd;AAEA,UAAM,cAAc,KAAK,IAAI,MAAM,SAAS;AAE5C,QAAI,SAAS;AACb,QAAI,SAAS,YAAY;AACvB,eAAS,yCAAyC,QAAQ,WAAW,KAAK,QAAQ,CAAC,CAAC,gDAAgD,WAAW;AAAA,IACjJ,WAAW,SAAS,aAAa;AAC/B,eAAS,uEAAuEA,KAAI;AAAA,IACtF,OAAO;AACL,eAAS,0CAA0C,QAAQ,gBAAgB,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,IACpG;AAEA,QAAI,QAAQ,yBAAyB,OAAO,wBAAwB,aAAa;AAC/E,gBAAU;AAAA,8CAAwC,OAAO,qBAAqB,oCAAoC,WAAW;AAAA,IAC/H;AAEA,WAAO;AAAA,MACL;AAAA,MACA,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,MACtB,sBAAsB,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,WAAW;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AACF;","names":["cpus"]}
|