@gravito/flux 1.0.0-alpha.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/README.md +295 -0
- package/dist/builder/WorkflowBuilder.d.ts +96 -0
- package/dist/builder/WorkflowBuilder.d.ts.map +1 -0
- package/dist/builder/index.d.ts +2 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/bun.d.ts +9 -0
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +7 -0
- package/dist/chunk-qjdtqchy.js +145 -0
- package/dist/core/ContextManager.d.ts +40 -0
- package/dist/core/ContextManager.d.ts.map +1 -0
- package/dist/core/StateMachine.d.ts +43 -0
- package/dist/core/StateMachine.d.ts.map +1 -0
- package/dist/core/StepExecutor.d.ts +34 -0
- package/dist/core/StepExecutor.d.ts.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/engine/FluxEngine.d.ts +66 -0
- package/dist/engine/FluxEngine.d.ts.map +1 -0
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +479 -0
- package/dist/index.node.d.ts +18 -0
- package/dist/index.node.d.ts.map +1 -0
- package/dist/logger/FluxLogger.d.ts +40 -0
- package/dist/logger/FluxLogger.d.ts.map +1 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/node/index.cjs +651 -0
- package/dist/node/index.mjs +619 -0
- package/dist/orbit/OrbitFlux.d.ts +107 -0
- package/dist/orbit/OrbitFlux.d.ts.map +1 -0
- package/dist/orbit/index.d.ts +2 -0
- package/dist/orbit/index.d.ts.map +1 -0
- package/dist/storage/BunSQLiteStorage.d.ts +73 -0
- package/dist/storage/BunSQLiteStorage.d.ts.map +1 -0
- package/dist/storage/MemoryStorage.d.ts +28 -0
- package/dist/storage/MemoryStorage.d.ts.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/types.d.ts +194 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
// src/builder/WorkflowBuilder.ts
|
|
2
|
+
class WorkflowBuilder {
|
|
3
|
+
_name;
|
|
4
|
+
_steps = [];
|
|
5
|
+
_validateInput;
|
|
6
|
+
constructor(name) {
|
|
7
|
+
this._name = name;
|
|
8
|
+
}
|
|
9
|
+
input() {
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
validate(validator) {
|
|
13
|
+
this._validateInput = validator;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
step(name, handler, options) {
|
|
17
|
+
this._steps.push({
|
|
18
|
+
name,
|
|
19
|
+
handler,
|
|
20
|
+
retries: options?.retries,
|
|
21
|
+
timeout: options?.timeout,
|
|
22
|
+
when: options?.when,
|
|
23
|
+
commit: false
|
|
24
|
+
});
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
commit(name, handler, options) {
|
|
28
|
+
this._steps.push({
|
|
29
|
+
name,
|
|
30
|
+
handler,
|
|
31
|
+
retries: options?.retries,
|
|
32
|
+
timeout: options?.timeout,
|
|
33
|
+
when: options?.when,
|
|
34
|
+
commit: true
|
|
35
|
+
});
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
build() {
|
|
39
|
+
if (this._steps.length === 0) {
|
|
40
|
+
throw new Error(`Workflow "${this._name}" has no steps`);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
name: this._name,
|
|
44
|
+
steps: [...this._steps],
|
|
45
|
+
validateInput: this._validateInput
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
get name() {
|
|
49
|
+
return this._name;
|
|
50
|
+
}
|
|
51
|
+
get stepCount() {
|
|
52
|
+
return this._steps.length;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function createWorkflow(name) {
|
|
56
|
+
return new WorkflowBuilder(name);
|
|
57
|
+
}
|
|
58
|
+
// src/core/ContextManager.ts
|
|
59
|
+
function generateId() {
|
|
60
|
+
return crypto.randomUUID();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class ContextManager {
|
|
64
|
+
create(name, input, stepCount) {
|
|
65
|
+
const history = Array.from({ length: stepCount }, (_, _i) => ({
|
|
66
|
+
name: "",
|
|
67
|
+
status: "pending",
|
|
68
|
+
retries: 0
|
|
69
|
+
}));
|
|
70
|
+
return {
|
|
71
|
+
id: generateId(),
|
|
72
|
+
name,
|
|
73
|
+
input,
|
|
74
|
+
data: {},
|
|
75
|
+
status: "pending",
|
|
76
|
+
currentStep: 0,
|
|
77
|
+
history
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
restore(state) {
|
|
81
|
+
return {
|
|
82
|
+
id: state.id,
|
|
83
|
+
name: state.name,
|
|
84
|
+
input: state.input,
|
|
85
|
+
data: { ...state.data },
|
|
86
|
+
status: state.status,
|
|
87
|
+
currentStep: state.currentStep,
|
|
88
|
+
history: state.history.map((h) => ({ ...h }))
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
toState(ctx) {
|
|
92
|
+
return {
|
|
93
|
+
id: ctx.id,
|
|
94
|
+
name: ctx.name,
|
|
95
|
+
status: ctx.status,
|
|
96
|
+
input: ctx.input,
|
|
97
|
+
data: { ...ctx.data },
|
|
98
|
+
currentStep: ctx.currentStep,
|
|
99
|
+
history: ctx.history.map((h) => ({ ...h })),
|
|
100
|
+
createdAt: new Date,
|
|
101
|
+
updatedAt: new Date
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
updateStatus(ctx, status) {
|
|
105
|
+
return {
|
|
106
|
+
...ctx,
|
|
107
|
+
status
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
advanceStep(ctx) {
|
|
111
|
+
return {
|
|
112
|
+
...ctx,
|
|
113
|
+
currentStep: ctx.currentStep + 1
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
setStepName(ctx, index, name) {
|
|
117
|
+
if (ctx.history[index]) {
|
|
118
|
+
ctx.history[index].name = name;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/core/StateMachine.ts
|
|
124
|
+
var TRANSITIONS = {
|
|
125
|
+
pending: ["running", "failed"],
|
|
126
|
+
running: ["paused", "completed", "failed"],
|
|
127
|
+
paused: ["running", "failed"],
|
|
128
|
+
completed: [],
|
|
129
|
+
failed: ["pending"]
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
class StateMachine extends EventTarget {
|
|
133
|
+
_status = "pending";
|
|
134
|
+
get status() {
|
|
135
|
+
return this._status;
|
|
136
|
+
}
|
|
137
|
+
canTransition(to) {
|
|
138
|
+
return TRANSITIONS[this._status].includes(to);
|
|
139
|
+
}
|
|
140
|
+
transition(to) {
|
|
141
|
+
if (!this.canTransition(to)) {
|
|
142
|
+
throw new Error(`Invalid state transition: ${this._status} → ${to}`);
|
|
143
|
+
}
|
|
144
|
+
const from = this._status;
|
|
145
|
+
this._status = to;
|
|
146
|
+
this.dispatchEvent(new CustomEvent("transition", {
|
|
147
|
+
detail: { from, to }
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
forceStatus(status) {
|
|
151
|
+
this._status = status;
|
|
152
|
+
}
|
|
153
|
+
isTerminal() {
|
|
154
|
+
return this._status === "completed" || this._status === "failed";
|
|
155
|
+
}
|
|
156
|
+
canExecute() {
|
|
157
|
+
return this._status === "pending" || this._status === "paused";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/core/StepExecutor.ts
|
|
162
|
+
class StepExecutor {
|
|
163
|
+
defaultRetries;
|
|
164
|
+
defaultTimeout;
|
|
165
|
+
constructor(options = {}) {
|
|
166
|
+
this.defaultRetries = options.defaultRetries ?? 3;
|
|
167
|
+
this.defaultTimeout = options.defaultTimeout ?? 30000;
|
|
168
|
+
}
|
|
169
|
+
async execute(step, ctx, execution) {
|
|
170
|
+
const maxRetries = step.retries ?? this.defaultRetries;
|
|
171
|
+
const timeout = step.timeout ?? this.defaultTimeout;
|
|
172
|
+
const startTime = Date.now();
|
|
173
|
+
if (step.when && !step.when(ctx)) {
|
|
174
|
+
execution.status = "skipped";
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
duration: 0
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
execution.status = "running";
|
|
181
|
+
execution.startedAt = new Date;
|
|
182
|
+
let lastError;
|
|
183
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
184
|
+
execution.retries = attempt;
|
|
185
|
+
try {
|
|
186
|
+
await this.executeWithTimeout(step.handler, ctx, timeout);
|
|
187
|
+
execution.status = "completed";
|
|
188
|
+
execution.completedAt = new Date;
|
|
189
|
+
execution.duration = Date.now() - startTime;
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
duration: execution.duration
|
|
193
|
+
};
|
|
194
|
+
} catch (error) {
|
|
195
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
196
|
+
if (attempt < maxRetries) {
|
|
197
|
+
await this.sleep(Math.min(1000 * 2 ** attempt, 1e4));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
execution.status = "failed";
|
|
202
|
+
execution.completedAt = new Date;
|
|
203
|
+
execution.duration = Date.now() - startTime;
|
|
204
|
+
execution.error = lastError?.message;
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: lastError,
|
|
208
|
+
duration: execution.duration
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async executeWithTimeout(handler, ctx, timeout) {
|
|
212
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
213
|
+
setTimeout(() => reject(new Error("Step timeout")), timeout);
|
|
214
|
+
});
|
|
215
|
+
await Promise.race([Promise.resolve(handler(ctx)), timeoutPromise]);
|
|
216
|
+
}
|
|
217
|
+
sleep(ms) {
|
|
218
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/storage/MemoryStorage.ts
|
|
223
|
+
class MemoryStorage {
|
|
224
|
+
store = new Map;
|
|
225
|
+
async save(state) {
|
|
226
|
+
this.store.set(state.id, {
|
|
227
|
+
...state,
|
|
228
|
+
updatedAt: new Date
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async load(id) {
|
|
232
|
+
return this.store.get(id) ?? null;
|
|
233
|
+
}
|
|
234
|
+
async list(filter) {
|
|
235
|
+
let results = Array.from(this.store.values());
|
|
236
|
+
if (filter?.name) {
|
|
237
|
+
results = results.filter((s) => s.name === filter.name);
|
|
238
|
+
}
|
|
239
|
+
if (filter?.status) {
|
|
240
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
241
|
+
results = results.filter((s) => statuses.includes(s.status));
|
|
242
|
+
}
|
|
243
|
+
results.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
244
|
+
if (filter?.offset) {
|
|
245
|
+
results = results.slice(filter.offset);
|
|
246
|
+
}
|
|
247
|
+
if (filter?.limit) {
|
|
248
|
+
results = results.slice(0, filter.limit);
|
|
249
|
+
}
|
|
250
|
+
return results;
|
|
251
|
+
}
|
|
252
|
+
async delete(id) {
|
|
253
|
+
this.store.delete(id);
|
|
254
|
+
}
|
|
255
|
+
async init() {}
|
|
256
|
+
async close() {
|
|
257
|
+
this.store.clear();
|
|
258
|
+
}
|
|
259
|
+
size() {
|
|
260
|
+
return this.store.size;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/engine/FluxEngine.ts
|
|
265
|
+
class FluxEngine {
|
|
266
|
+
storage;
|
|
267
|
+
executor;
|
|
268
|
+
contextManager;
|
|
269
|
+
config;
|
|
270
|
+
constructor(config = {}) {
|
|
271
|
+
this.config = config;
|
|
272
|
+
this.storage = config.storage ?? new MemoryStorage;
|
|
273
|
+
this.executor = new StepExecutor({
|
|
274
|
+
defaultRetries: config.defaultRetries,
|
|
275
|
+
defaultTimeout: config.defaultTimeout
|
|
276
|
+
});
|
|
277
|
+
this.contextManager = new ContextManager;
|
|
278
|
+
}
|
|
279
|
+
async execute(workflow, input) {
|
|
280
|
+
const startTime = Date.now();
|
|
281
|
+
const definition = workflow instanceof WorkflowBuilder ? workflow.build() : workflow;
|
|
282
|
+
if (definition.validateInput && !definition.validateInput(input)) {
|
|
283
|
+
throw new Error(`Invalid input for workflow "${definition.name}"`);
|
|
284
|
+
}
|
|
285
|
+
const ctx = this.contextManager.create(definition.name, input, definition.steps.length);
|
|
286
|
+
const stateMachine = new StateMachine;
|
|
287
|
+
await this.storage.save(this.contextManager.toState(ctx));
|
|
288
|
+
try {
|
|
289
|
+
stateMachine.transition("running");
|
|
290
|
+
Object.assign(ctx, { status: "running" });
|
|
291
|
+
for (let i = 0;i < definition.steps.length; i++) {
|
|
292
|
+
const step = definition.steps[i];
|
|
293
|
+
const execution = ctx.history[i];
|
|
294
|
+
this.contextManager.setStepName(ctx, i, step.name);
|
|
295
|
+
Object.assign(ctx, { currentStep: i });
|
|
296
|
+
this.config.on?.stepStart?.(step.name, ctx);
|
|
297
|
+
const result = await this.executor.execute(step, ctx, execution);
|
|
298
|
+
if (result.success) {
|
|
299
|
+
this.config.on?.stepComplete?.(step.name, ctx, result);
|
|
300
|
+
} else {
|
|
301
|
+
this.config.on?.stepError?.(step.name, ctx, result.error);
|
|
302
|
+
stateMachine.transition("failed");
|
|
303
|
+
Object.assign(ctx, { status: "failed" });
|
|
304
|
+
await this.storage.save({
|
|
305
|
+
...this.contextManager.toState(ctx),
|
|
306
|
+
error: result.error?.message
|
|
307
|
+
});
|
|
308
|
+
return {
|
|
309
|
+
id: ctx.id,
|
|
310
|
+
status: "failed",
|
|
311
|
+
data: ctx.data,
|
|
312
|
+
history: ctx.history,
|
|
313
|
+
duration: Date.now() - startTime,
|
|
314
|
+
error: result.error
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
await this.storage.save(this.contextManager.toState(ctx));
|
|
318
|
+
}
|
|
319
|
+
stateMachine.transition("completed");
|
|
320
|
+
Object.assign(ctx, { status: "completed" });
|
|
321
|
+
await this.storage.save({
|
|
322
|
+
...this.contextManager.toState(ctx),
|
|
323
|
+
completedAt: new Date
|
|
324
|
+
});
|
|
325
|
+
this.config.on?.workflowComplete?.(ctx);
|
|
326
|
+
return {
|
|
327
|
+
id: ctx.id,
|
|
328
|
+
status: "completed",
|
|
329
|
+
data: ctx.data,
|
|
330
|
+
history: ctx.history,
|
|
331
|
+
duration: Date.now() - startTime
|
|
332
|
+
};
|
|
333
|
+
} catch (error) {
|
|
334
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
335
|
+
this.config.on?.workflowError?.(ctx, err);
|
|
336
|
+
stateMachine.forceStatus("failed");
|
|
337
|
+
Object.assign(ctx, { status: "failed" });
|
|
338
|
+
await this.storage.save({
|
|
339
|
+
...this.contextManager.toState(ctx),
|
|
340
|
+
error: err.message
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
id: ctx.id,
|
|
344
|
+
status: "failed",
|
|
345
|
+
data: ctx.data,
|
|
346
|
+
history: ctx.history,
|
|
347
|
+
duration: Date.now() - startTime,
|
|
348
|
+
error: err
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async resume(workflowId) {
|
|
353
|
+
const state = await this.storage.load(workflowId);
|
|
354
|
+
if (!state) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
throw new Error("Resume not yet implemented");
|
|
358
|
+
}
|
|
359
|
+
async get(workflowId) {
|
|
360
|
+
return this.storage.load(workflowId);
|
|
361
|
+
}
|
|
362
|
+
async list(filter) {
|
|
363
|
+
return this.storage.list(filter);
|
|
364
|
+
}
|
|
365
|
+
async init() {
|
|
366
|
+
await this.storage.init?.();
|
|
367
|
+
}
|
|
368
|
+
async close() {
|
|
369
|
+
await this.storage.close?.();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// src/logger/FluxLogger.ts
|
|
373
|
+
class FluxConsoleLogger {
|
|
374
|
+
prefix;
|
|
375
|
+
constructor(prefix = "[Flux]") {
|
|
376
|
+
this.prefix = prefix;
|
|
377
|
+
}
|
|
378
|
+
debug(message, ...args) {
|
|
379
|
+
console.debug(`${this.prefix} ${message}`, ...args);
|
|
380
|
+
}
|
|
381
|
+
info(message, ...args) {
|
|
382
|
+
console.info(`${this.prefix} ${message}`, ...args);
|
|
383
|
+
}
|
|
384
|
+
warn(message, ...args) {
|
|
385
|
+
console.warn(`${this.prefix} ${message}`, ...args);
|
|
386
|
+
}
|
|
387
|
+
error(message, ...args) {
|
|
388
|
+
console.error(`${this.prefix} ${message}`, ...args);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class FluxSilentLogger {
|
|
393
|
+
debug() {}
|
|
394
|
+
info() {}
|
|
395
|
+
warn() {}
|
|
396
|
+
error() {}
|
|
397
|
+
}
|
|
398
|
+
// src/storage/BunSQLiteStorage.ts
|
|
399
|
+
import { Database } from "bun:sqlite";
|
|
400
|
+
|
|
401
|
+
class BunSQLiteStorage {
|
|
402
|
+
db;
|
|
403
|
+
tableName;
|
|
404
|
+
initialized = false;
|
|
405
|
+
constructor(options = {}) {
|
|
406
|
+
this.db = new Database(options.path ?? ":memory:");
|
|
407
|
+
this.tableName = options.tableName ?? "flux_workflows";
|
|
408
|
+
}
|
|
409
|
+
async init() {
|
|
410
|
+
if (this.initialized) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this.db.run(`
|
|
414
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
415
|
+
id TEXT PRIMARY KEY,
|
|
416
|
+
name TEXT NOT NULL,
|
|
417
|
+
status TEXT NOT NULL,
|
|
418
|
+
input TEXT NOT NULL,
|
|
419
|
+
data TEXT NOT NULL,
|
|
420
|
+
current_step INTEGER NOT NULL,
|
|
421
|
+
history TEXT NOT NULL,
|
|
422
|
+
error TEXT,
|
|
423
|
+
created_at TEXT NOT NULL,
|
|
424
|
+
updated_at TEXT NOT NULL,
|
|
425
|
+
completed_at TEXT
|
|
426
|
+
)
|
|
427
|
+
`);
|
|
428
|
+
this.db.run(`
|
|
429
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_name
|
|
430
|
+
ON ${this.tableName}(name)
|
|
431
|
+
`);
|
|
432
|
+
this.db.run(`
|
|
433
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_status
|
|
434
|
+
ON ${this.tableName}(status)
|
|
435
|
+
`);
|
|
436
|
+
this.db.run(`
|
|
437
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_created
|
|
438
|
+
ON ${this.tableName}(created_at DESC)
|
|
439
|
+
`);
|
|
440
|
+
this.initialized = true;
|
|
441
|
+
}
|
|
442
|
+
async save(state) {
|
|
443
|
+
await this.init();
|
|
444
|
+
const stmt = this.db.prepare(`
|
|
445
|
+
INSERT OR REPLACE INTO ${this.tableName}
|
|
446
|
+
(id, name, status, input, data, current_step, history, error, created_at, updated_at, completed_at)
|
|
447
|
+
VALUES ($id, $name, $status, $input, $data, $currentStep, $history, $error, $createdAt, $updatedAt, $completedAt)
|
|
448
|
+
`);
|
|
449
|
+
stmt.run({
|
|
450
|
+
$id: state.id,
|
|
451
|
+
$name: state.name,
|
|
452
|
+
$status: state.status,
|
|
453
|
+
$input: JSON.stringify(state.input),
|
|
454
|
+
$data: JSON.stringify(state.data),
|
|
455
|
+
$currentStep: state.currentStep,
|
|
456
|
+
$history: JSON.stringify(state.history),
|
|
457
|
+
$error: state.error ?? null,
|
|
458
|
+
$createdAt: state.createdAt.toISOString(),
|
|
459
|
+
$updatedAt: state.updatedAt.toISOString(),
|
|
460
|
+
$completedAt: state.completedAt?.toISOString() ?? null
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
async load(id) {
|
|
464
|
+
await this.init();
|
|
465
|
+
const stmt = this.db.prepare(`
|
|
466
|
+
SELECT * FROM ${this.tableName} WHERE id = $id
|
|
467
|
+
`);
|
|
468
|
+
const row = stmt.get({ $id: id });
|
|
469
|
+
if (!row) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
return this.rowToState(row);
|
|
473
|
+
}
|
|
474
|
+
async list(filter) {
|
|
475
|
+
await this.init();
|
|
476
|
+
let query = `SELECT * FROM ${this.tableName} WHERE 1=1`;
|
|
477
|
+
const params = {};
|
|
478
|
+
if (filter?.name) {
|
|
479
|
+
query += " AND name = $name";
|
|
480
|
+
params.$name = filter.name;
|
|
481
|
+
}
|
|
482
|
+
if (filter?.status) {
|
|
483
|
+
if (Array.isArray(filter.status)) {
|
|
484
|
+
const placeholders = filter.status.map((_, i) => `$status${i}`).join(", ");
|
|
485
|
+
query += ` AND status IN (${placeholders})`;
|
|
486
|
+
filter.status.forEach((s, i) => {
|
|
487
|
+
params[`$status${i}`] = s;
|
|
488
|
+
});
|
|
489
|
+
} else {
|
|
490
|
+
query += " AND status = $status";
|
|
491
|
+
params.$status = filter.status;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
query += " ORDER BY created_at DESC";
|
|
495
|
+
if (filter?.limit) {
|
|
496
|
+
query += " LIMIT $limit";
|
|
497
|
+
params.$limit = filter.limit;
|
|
498
|
+
}
|
|
499
|
+
if (filter?.offset) {
|
|
500
|
+
query += " OFFSET $offset";
|
|
501
|
+
params.$offset = filter.offset;
|
|
502
|
+
}
|
|
503
|
+
const stmt = this.db.prepare(query);
|
|
504
|
+
const rows = stmt.all(params);
|
|
505
|
+
return rows.map((row) => this.rowToState(row));
|
|
506
|
+
}
|
|
507
|
+
async delete(id) {
|
|
508
|
+
await this.init();
|
|
509
|
+
const stmt = this.db.prepare(`
|
|
510
|
+
DELETE FROM ${this.tableName} WHERE id = $id
|
|
511
|
+
`);
|
|
512
|
+
stmt.run({ $id: id });
|
|
513
|
+
}
|
|
514
|
+
async close() {
|
|
515
|
+
this.db.close();
|
|
516
|
+
this.initialized = false;
|
|
517
|
+
}
|
|
518
|
+
rowToState(row) {
|
|
519
|
+
return {
|
|
520
|
+
id: row.id,
|
|
521
|
+
name: row.name,
|
|
522
|
+
status: row.status,
|
|
523
|
+
input: JSON.parse(row.input),
|
|
524
|
+
data: JSON.parse(row.data),
|
|
525
|
+
currentStep: row.current_step,
|
|
526
|
+
history: JSON.parse(row.history),
|
|
527
|
+
error: row.error ?? undefined,
|
|
528
|
+
createdAt: new Date(row.created_at),
|
|
529
|
+
updatedAt: new Date(row.updated_at),
|
|
530
|
+
completedAt: row.completed_at ? new Date(row.completed_at) : undefined
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
getDatabase() {
|
|
534
|
+
return this.db;
|
|
535
|
+
}
|
|
536
|
+
vacuum() {
|
|
537
|
+
this.db.run("VACUUM");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/orbit/OrbitFlux.ts
|
|
542
|
+
class OrbitFlux {
|
|
543
|
+
options;
|
|
544
|
+
engine;
|
|
545
|
+
constructor(options = {}) {
|
|
546
|
+
this.options = {
|
|
547
|
+
storage: "memory",
|
|
548
|
+
exposeAs: "flux",
|
|
549
|
+
defaultRetries: 3,
|
|
550
|
+
defaultTimeout: 30000,
|
|
551
|
+
...options
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
static configure(options = {}) {
|
|
555
|
+
return new OrbitFlux(options);
|
|
556
|
+
}
|
|
557
|
+
async install(core) {
|
|
558
|
+
const { storage, dbPath, exposeAs, defaultRetries, defaultTimeout, logger } = this.options;
|
|
559
|
+
let storageAdapter;
|
|
560
|
+
if (typeof storage === "string") {
|
|
561
|
+
switch (storage) {
|
|
562
|
+
case "sqlite":
|
|
563
|
+
storageAdapter = new BunSQLiteStorage({ path: dbPath });
|
|
564
|
+
break;
|
|
565
|
+
default:
|
|
566
|
+
storageAdapter = new MemoryStorage;
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
storageAdapter = storage;
|
|
570
|
+
}
|
|
571
|
+
await storageAdapter.init?.();
|
|
572
|
+
const engineConfig = {
|
|
573
|
+
storage: storageAdapter,
|
|
574
|
+
defaultRetries,
|
|
575
|
+
defaultTimeout,
|
|
576
|
+
logger: logger ?? {
|
|
577
|
+
debug: (msg) => core.logger.debug(`[Flux] ${msg}`),
|
|
578
|
+
info: (msg) => core.logger.info(`[Flux] ${msg}`),
|
|
579
|
+
warn: (msg) => core.logger.warn(`[Flux] ${msg}`),
|
|
580
|
+
error: (msg) => core.logger.error(`[Flux] ${msg}`)
|
|
581
|
+
},
|
|
582
|
+
on: {
|
|
583
|
+
stepStart: (step) => {
|
|
584
|
+
core.hooks.doAction("flux:step:start", { step });
|
|
585
|
+
},
|
|
586
|
+
stepComplete: (step, ctx, result) => {
|
|
587
|
+
core.hooks.doAction("flux:step:complete", { step, ctx, result });
|
|
588
|
+
},
|
|
589
|
+
stepError: (step, ctx, error) => {
|
|
590
|
+
core.hooks.doAction("flux:step:error", { step, ctx, error });
|
|
591
|
+
},
|
|
592
|
+
workflowComplete: (ctx) => {
|
|
593
|
+
core.hooks.doAction("flux:workflow:complete", { ctx });
|
|
594
|
+
},
|
|
595
|
+
workflowError: (ctx, error) => {
|
|
596
|
+
core.hooks.doAction("flux:workflow:error", { ctx, error });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
this.engine = new FluxEngine(engineConfig);
|
|
601
|
+
core.services.set(exposeAs, this.engine);
|
|
602
|
+
core.logger.info(`[OrbitFlux] Initialized (Storage: ${typeof storage === "string" ? storage : "custom"})`);
|
|
603
|
+
}
|
|
604
|
+
getEngine() {
|
|
605
|
+
return this.engine;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
export {
|
|
609
|
+
createWorkflow,
|
|
610
|
+
WorkflowBuilder,
|
|
611
|
+
StepExecutor,
|
|
612
|
+
StateMachine,
|
|
613
|
+
OrbitFlux,
|
|
614
|
+
MemoryStorage,
|
|
615
|
+
FluxSilentLogger,
|
|
616
|
+
FluxEngine,
|
|
617
|
+
FluxConsoleLogger,
|
|
618
|
+
ContextManager
|
|
619
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview OrbitFlux - Gravito PlanetCore Integration
|
|
3
|
+
*
|
|
4
|
+
* Integrates FluxEngine with Gravito's PlanetCore for seamless workflow management.
|
|
5
|
+
*
|
|
6
|
+
* @module @gravito/flux
|
|
7
|
+
*/
|
|
8
|
+
import { FluxEngine } from '../engine/FluxEngine';
|
|
9
|
+
import type { FluxLogger, WorkflowStorage } from '../types';
|
|
10
|
+
/**
|
|
11
|
+
* Minimal PlanetCore interface for type compatibility
|
|
12
|
+
* (Avoids importing gravito-core sources which causes rootDir issues)
|
|
13
|
+
*/
|
|
14
|
+
interface PlanetCore {
|
|
15
|
+
logger: {
|
|
16
|
+
debug(message: string, ...args: unknown[]): void;
|
|
17
|
+
info(message: string, ...args: unknown[]): void;
|
|
18
|
+
warn(message: string, ...args: unknown[]): void;
|
|
19
|
+
error(message: string, ...args: unknown[]): void;
|
|
20
|
+
};
|
|
21
|
+
services: {
|
|
22
|
+
set(key: string, value: unknown): void;
|
|
23
|
+
get<T>(key: string): T | undefined;
|
|
24
|
+
};
|
|
25
|
+
hooks: {
|
|
26
|
+
doAction(name: string, payload?: unknown): void;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* GravitoOrbit interface
|
|
31
|
+
*/
|
|
32
|
+
export interface GravitoOrbit {
|
|
33
|
+
install(core: PlanetCore): void | Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* OrbitFlux configuration options
|
|
37
|
+
*/
|
|
38
|
+
export interface OrbitFluxOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Storage driver: 'memory' | 'sqlite' | custom WorkflowStorage
|
|
41
|
+
* @default 'memory'
|
|
42
|
+
*/
|
|
43
|
+
storage?: 'memory' | 'sqlite' | WorkflowStorage;
|
|
44
|
+
/**
|
|
45
|
+
* SQLite database path (only used if storage is 'sqlite')
|
|
46
|
+
* @default ':memory:'
|
|
47
|
+
*/
|
|
48
|
+
dbPath?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Service name in core.services
|
|
51
|
+
* @default 'flux'
|
|
52
|
+
*/
|
|
53
|
+
exposeAs?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Custom logger
|
|
56
|
+
*/
|
|
57
|
+
logger?: FluxLogger;
|
|
58
|
+
/**
|
|
59
|
+
* Default retry count for steps
|
|
60
|
+
* @default 3
|
|
61
|
+
*/
|
|
62
|
+
defaultRetries?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Default timeout for steps (ms)
|
|
65
|
+
* @default 30000
|
|
66
|
+
*/
|
|
67
|
+
defaultTimeout?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* OrbitFlux - Gravito Workflow Integration
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { OrbitFlux } from '@gravito/flux'
|
|
75
|
+
*
|
|
76
|
+
* const core = await PlanetCore.boot({
|
|
77
|
+
* orbits: [
|
|
78
|
+
* new OrbitFlux({ storage: 'sqlite', dbPath: './data/workflows.db' })
|
|
79
|
+
* ]
|
|
80
|
+
* })
|
|
81
|
+
*
|
|
82
|
+
* // Access via services
|
|
83
|
+
* const flux = core.services.get<FluxEngine>('flux')
|
|
84
|
+
* await flux.execute(myWorkflow, input)
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare class OrbitFlux implements GravitoOrbit {
|
|
88
|
+
private options;
|
|
89
|
+
private engine?;
|
|
90
|
+
constructor(options?: OrbitFluxOptions);
|
|
91
|
+
/**
|
|
92
|
+
* Create OrbitFlux with configuration
|
|
93
|
+
*/
|
|
94
|
+
static configure(options?: OrbitFluxOptions): OrbitFlux;
|
|
95
|
+
/**
|
|
96
|
+
* Install into PlanetCore
|
|
97
|
+
*
|
|
98
|
+
* @param core - The PlanetCore instance
|
|
99
|
+
*/
|
|
100
|
+
install(core: PlanetCore): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Get the FluxEngine instance
|
|
103
|
+
*/
|
|
104
|
+
getEngine(): FluxEngine | undefined;
|
|
105
|
+
}
|
|
106
|
+
export {};
|
|
107
|
+
//# sourceMappingURL=OrbitFlux.d.ts.map
|