@gravito/flux 3.0.2 → 3.0.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BunSQLiteStorage
3
- } from "./chunk-NAIVO7RR.js";
3
+ } from "./chunk-EZGSU6AW.js";
4
4
 
5
5
  // src/errors.ts
6
6
  var FluxErrorCode = /* @__PURE__ */ ((FluxErrorCode2) => {
@@ -1544,7 +1544,11 @@ function resetHistoryFrom(ctx, startIndex) {
1544
1544
  entry.status = "pending";
1545
1545
  entry.startedAt = void 0;
1546
1546
  entry.completedAt = void 0;
1547
+ entry.suspendedAt = void 0;
1548
+ entry.compensatedAt = void 0;
1549
+ entry.waitingFor = void 0;
1547
1550
  entry.duration = void 0;
1551
+ entry.output = void 0;
1548
1552
  entry.error = void 0;
1549
1553
  entry.retries = 0;
1550
1554
  }
@@ -1954,6 +1958,7 @@ var RollbackManager = class {
1954
1958
  error: originalError.message
1955
1959
  });
1956
1960
  let compensatedCount = 0;
1961
+ let skippedCount = 0;
1957
1962
  for (let i = failedAtIndex - 1; i >= 0; i--) {
1958
1963
  const step = definition.steps[i];
1959
1964
  let execution = currentCtx.history[i];
@@ -1988,11 +1993,36 @@ var RollbackManager = class {
1988
1993
  } catch (err) {
1989
1994
  const error = err instanceof Error ? err : new Error(String(err));
1990
1995
  const action = this.recoveryManager.getAction(step.name);
1996
+ if (action?.type === "retry") {
1997
+ const retryResult = await new CompensationRetryPolicy({
1998
+ ...this.retryPolicy.getConfig(),
1999
+ maxAttempts: action.maxAttempts ?? this.retryPolicy.getConfig().maxAttempts
2000
+ }).execute(async () => {
2001
+ await step.compensate?.(currentCtx);
2002
+ });
2003
+ if (retryResult.success) {
2004
+ execution = { ...execution, status: "compensated", compensatedAt: /* @__PURE__ */ new Date() };
2005
+ currentCtx = updateWorkflowContext(currentCtx, {
2006
+ history: currentCtx.history.map((h, idx) => idx === i ? execution : h)
2007
+ });
2008
+ compensatedCount++;
2009
+ await this.traceEmitter.stepCompensate(currentCtx, step.name, i);
2010
+ currentCtx = await this.persist(currentCtx);
2011
+ continue;
2012
+ }
2013
+ }
1991
2014
  if (action?.type === "skip") {
2015
+ execution = { ...execution, status: "completed", error: error.message };
2016
+ currentCtx = updateWorkflowContext(currentCtx, {
2017
+ history: currentCtx.history.map((h, idx) => idx === i ? execution : h)
2018
+ });
2019
+ currentCtx = await this.persist(currentCtx);
2020
+ skippedCount++;
1992
2021
  continue;
1993
2022
  }
1994
2023
  if (action?.type === "abort") {
1995
2024
  currentCtx = updateWorkflowContext(currentCtx, { status: "compensation_failed" });
2025
+ currentCtx = await this.persist(currentCtx);
1996
2026
  await this.traceEmitter.emit({
1997
2027
  type: "workflow:error",
1998
2028
  timestamp: Date.now(),
@@ -2005,6 +2035,7 @@ var RollbackManager = class {
2005
2035
  }
2006
2036
  await this.recoveryManager.notifyRecoveryNeeded(currentCtx, step.name, error);
2007
2037
  currentCtx = updateWorkflowContext(currentCtx, { status: "compensation_failed" });
2038
+ currentCtx = await this.persist(currentCtx);
2008
2039
  await this.traceEmitter.emit({
2009
2040
  type: "workflow:error",
2010
2041
  timestamp: Date.now(),
@@ -2017,7 +2048,7 @@ var RollbackManager = class {
2017
2048
  }
2018
2049
  currentCtx = await this.persist(currentCtx);
2019
2050
  }
2020
- if (compensatedCount > 0) {
2051
+ if (compensatedCount > 0 || skippedCount > 0) {
2021
2052
  currentCtx = updateWorkflowContext(currentCtx, { status: "rolled_back" });
2022
2053
  await this.traceEmitter.emit({
2023
2054
  type: "workflow:rollback_complete",
@@ -2712,7 +2743,14 @@ var FluxEngine = class {
2712
2743
  }
2713
2744
  ctx = updateWorkflowContext(ctx, {
2714
2745
  history: ctx.history.map(
2715
- (h, i) => i === idx ? { ...h, status: "completed", completedAt: /* @__PURE__ */ new Date(), output: payload } : h
2746
+ (h, i) => i === idx ? {
2747
+ ...h,
2748
+ status: "completed",
2749
+ completedAt: /* @__PURE__ */ new Date(),
2750
+ suspendedAt: void 0,
2751
+ waitingFor: void 0,
2752
+ output: payload
2753
+ } : h
2716
2754
  )
2717
2755
  });
2718
2756
  await this.traceEmitter.emit({
@@ -2831,7 +2869,7 @@ var PostgreSQLStorage = class {
2831
2869
  */
2832
2870
  constructor(options = {}) {
2833
2871
  this.options = options;
2834
- this.tableName = options.tableName ?? "flux_workflows";
2872
+ this.tableName = validateSqlIdentifier(options.tableName ?? "flux_workflows", "tableName");
2835
2873
  }
2836
2874
  /**
2837
2875
  * Initializes the database connection pool and schema.
@@ -3056,20 +3094,34 @@ var PostgreSQLStorage = class {
3056
3094
  await this.pool.query("VACUUM ANALYZE");
3057
3095
  }
3058
3096
  };
3097
+ function validateSqlIdentifier(value, field) {
3098
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
3099
+ throw new Error(
3100
+ `Invalid ${field}: "${value}". Only letters, numbers, and underscores are allowed.`
3101
+ );
3102
+ }
3103
+ return value;
3104
+ }
3059
3105
 
3060
3106
  // src/trace/JsonFileTraceSink.ts
3061
- import { appendFile, mkdir, writeFile } from "fs/promises";
3107
+ import { mkdir } from "fs/promises";
3062
3108
  import { dirname } from "path";
3109
+ import { getDefaultRuntimeAdapter } from "@gravito/core";
3063
3110
  var JsonFileTraceSink = class {
3064
3111
  path;
3065
3112
  ready;
3113
+ fileSink = null;
3114
+ buffer = [];
3115
+ bufferSize;
3116
+ adapter = getDefaultRuntimeAdapter();
3066
3117
  /**
3067
- * Creates a new JSON file trace sink.
3118
+ * Creates a new JSON file trace sink with buffering support.
3068
3119
  *
3069
3120
  * @param options - Configuration options for the sink.
3070
3121
  */
3071
3122
  constructor(options) {
3072
3123
  this.path = options.path;
3124
+ this.bufferSize = options.bufferSize ?? 50;
3073
3125
  this.ready = this.init(options.reset ?? true);
3074
3126
  }
3075
3127
  /**
@@ -3080,14 +3132,22 @@ var JsonFileTraceSink = class {
3080
3132
  */
3081
3133
  async init(reset) {
3082
3134
  await mkdir(dirname(this.path), { recursive: true });
3083
- if (reset) {
3084
- await writeFile(this.path, "", "utf8");
3135
+ if (this.adapter.createFileSink) {
3136
+ this.fileSink = await this.adapter.createFileSink?.(this.path);
3137
+ }
3138
+ if (reset && !this.fileSink) {
3139
+ if (this.adapter.writeFile) {
3140
+ await this.adapter.writeFile(this.path, "");
3141
+ }
3142
+ } else if (reset && this.fileSink) {
3143
+ this.fileSink.write("");
3085
3144
  }
3086
3145
  }
3087
3146
  /**
3088
3147
  * Appends a trace event to the file in NDJSON format.
3089
3148
  *
3090
- * Waits for initialization to complete before writing.
3149
+ * Events are buffered and written in batches using FileSink
3150
+ * for improved I/O efficiency. Waits for initialization to complete before writing.
3091
3151
  *
3092
3152
  * @param event - The trace event to record.
3093
3153
  * @throws {Error} If writing to the file fails.
@@ -3104,8 +3164,48 @@ var JsonFileTraceSink = class {
3104
3164
  */
3105
3165
  async emit(event) {
3106
3166
  await this.ready;
3107
- await appendFile(this.path, `${JSON.stringify(event)}
3108
- `, "utf8");
3167
+ const eventLine = `${JSON.stringify(event)}
3168
+ `;
3169
+ if (this.fileSink) {
3170
+ this.buffer.push(eventLine);
3171
+ if (this.buffer.length >= this.bufferSize) {
3172
+ await this.flushBuffer();
3173
+ }
3174
+ } else {
3175
+ if (this.adapter.appendFile) {
3176
+ await this.adapter.appendFile(this.path, eventLine);
3177
+ }
3178
+ }
3179
+ }
3180
+ /**
3181
+ * Flushes buffered events to disk.
3182
+ *
3183
+ * This is called automatically when the buffer reaches the configured size.
3184
+ * Can also be called manually to ensure all events are written.
3185
+ *
3186
+ * @throws {Error} If flushing fails.
3187
+ */
3188
+ async flushBuffer() {
3189
+ if (!this.fileSink || this.buffer.length === 0) {
3190
+ return;
3191
+ }
3192
+ const content = this.buffer.join("");
3193
+ this.fileSink.write(content);
3194
+ this.buffer = [];
3195
+ await this.fileSink.flush();
3196
+ }
3197
+ /**
3198
+ * Closes the sink and flushes any remaining buffered events.
3199
+ *
3200
+ * Should be called before process exit to ensure all events are persisted.
3201
+ *
3202
+ * @throws {Error} If closing or flushing fails.
3203
+ */
3204
+ async close() {
3205
+ await this.flushBuffer();
3206
+ if (this.fileSink) {
3207
+ await this.fileSink.end();
3208
+ }
3109
3209
  }
3110
3210
  };
3111
3211
 
@@ -3483,4 +3583,4 @@ export {
3483
3583
  FluxSilentLogger,
3484
3584
  OrbitFlux
3485
3585
  };
3486
- //# sourceMappingURL=chunk-4DXCQ6CL.js.map
3586
+ //# sourceMappingURL=chunk-M2ZRQRF4.js.map