@smithers-orchestrator/engine 0.16.0
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/LICENSE +21 -0
- package/package.json +50 -0
- package/src/AlertHumanRequestOptions.ts +8 -0
- package/src/AlertRuntimeServices.ts +10 -0
- package/src/ChildWorkflowDefinition.ts +5 -0
- package/src/ChildWorkflowExecuteOptions.ts +14 -0
- package/src/ContinuationRequest.ts +3 -0
- package/src/HijackState.ts +19 -0
- package/src/HumanRequestKind.ts +1 -0
- package/src/HumanRequestStatus.ts +1 -0
- package/src/PlanNode.ts +29 -0
- package/src/RalphMeta.ts +7 -0
- package/src/RalphState.ts +4 -0
- package/src/RalphStateMap.ts +3 -0
- package/src/ScheduleResult.ts +15 -0
- package/src/SignalRunOptions.ts +5 -0
- package/src/alert-runtime.js +22 -0
- package/src/approvals.js +220 -0
- package/src/child-workflow.js +163 -0
- package/src/effect/ApprovalDeferredResolution.ts +13 -0
- package/src/effect/ApprovalDurableDeferredResolution.ts +11 -0
- package/src/effect/ApprovalPayload.ts +7 -0
- package/src/effect/ApprovalResult.ts +6 -0
- package/src/effect/BuilderNode.ts +52 -0
- package/src/effect/BuilderStepHandle.ts +47 -0
- package/src/effect/CancelPayload.ts +3 -0
- package/src/effect/CancelResult.ts +4 -0
- package/src/effect/DeferredResolution.ts +7 -0
- package/src/effect/DiffBundle.ts +7 -0
- package/src/effect/ExecuteTaskActivityOptions.ts +7 -0
- package/src/effect/FilePatch.ts +6 -0
- package/src/effect/GetRunPayload.ts +3 -0
- package/src/effect/GetRunResult.ts +3 -0
- package/src/effect/LegacyExecuteTaskFn.ts +24 -0
- package/src/effect/ListRunsPayload.ts +6 -0
- package/src/effect/RunStatusSchema.ts +9 -0
- package/src/effect/RunSummary.ts +23 -0
- package/src/effect/SignalPayload.ts +7 -0
- package/src/effect/SignalResult.ts +6 -0
- package/src/effect/SmithersSqliteOptions.ts +3 -0
- package/src/effect/SqlMessageStorageEventHistoryQuery.ts +7 -0
- package/src/effect/TaggedWorkerError.ts +46 -0
- package/src/effect/TaskActivityContext.ts +4 -0
- package/src/effect/TaskActivityRetryOptions.ts +4 -0
- package/src/effect/TaskBridgeToolConfig.ts +6 -0
- package/src/effect/TaskFailure.ts +3 -0
- package/src/effect/TaskResult.ts +5 -0
- package/src/effect/UnknownWorkerError.ts +5 -0
- package/src/effect/WaitForEventDurableDeferredResolution.ts +11 -0
- package/src/effect/WorkerDispatchKind.ts +1 -0
- package/src/effect/WorkerTask.ts +14 -0
- package/src/effect/WorkerTaskError.ts +4 -0
- package/src/effect/WorkerTaskKind.ts +1 -0
- package/src/effect/WorkflowPatchDecisionRecord.ts +4 -0
- package/src/effect/WorkflowPatchDecisions.ts +1 -0
- package/src/effect/WorkflowVersioningRuntime.ts +7 -0
- package/src/effect/activity-bridge.js +131 -0
- package/src/effect/bridge-utils.js +45 -0
- package/src/effect/builder.js +837 -0
- package/src/effect/compute-task-bridge.js +734 -0
- package/src/effect/deferred-bridge.js +63 -0
- package/src/effect/deferred-state-bridge.js +1343 -0
- package/src/effect/diff-bundle.js +352 -0
- package/src/effect/durable-deferred-bridge.js +282 -0
- package/src/effect/entity-worker.js +154 -0
- package/src/effect/http-runner.js +86 -0
- package/src/effect/rpc-schema.js +101 -0
- package/src/effect/single-runner.js +189 -0
- package/src/effect/sql-message-storage.js +817 -0
- package/src/effect/static-task-bridge.js +308 -0
- package/src/effect/versioning.js +123 -0
- package/src/effect/workflow-bridge.js +260 -0
- package/src/effect/workflow-make-bridge.js +233 -0
- package/src/engine.js +6933 -0
- package/src/events.js +237 -0
- package/src/external/json-schema-to-zod.js +214 -0
- package/src/getDefinedToolMetadata.js +10 -0
- package/src/hot/HotReloadEvent.ts +21 -0
- package/src/hot/HotWorkflowController.js +220 -0
- package/src/hot/OverlayOptions.ts +4 -0
- package/src/hot/WatchTreeOptions.ts +6 -0
- package/src/hot/index.js +9 -0
- package/src/hot/overlay.js +177 -0
- package/src/hot/watch.js +174 -0
- package/src/human-requests.js +120 -0
- package/src/index.d.ts +1597 -0
- package/src/index.js +41 -0
- package/src/runtime-owner.js +36 -0
- package/src/scheduler.js +31 -0
- package/src/signals.js +82 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { BuilderStepHandle } from "./BuilderStepHandle.ts";
|
|
2
|
+
|
|
3
|
+
type SequenceNode = {
|
|
4
|
+
kind: "sequence";
|
|
5
|
+
children: BuilderNode[];
|
|
6
|
+
};
|
|
7
|
+
type ParallelNode = {
|
|
8
|
+
kind: "parallel";
|
|
9
|
+
children: BuilderNode[];
|
|
10
|
+
maxConcurrency?: number;
|
|
11
|
+
};
|
|
12
|
+
type LoopNode = {
|
|
13
|
+
kind: "loop";
|
|
14
|
+
id?: string;
|
|
15
|
+
children: BuilderNode;
|
|
16
|
+
until: (outputs: Record<string, unknown>) => boolean;
|
|
17
|
+
maxIterations?: number;
|
|
18
|
+
onMaxReached?: "fail" | "return-last";
|
|
19
|
+
handles?: BuilderStepHandle[];
|
|
20
|
+
};
|
|
21
|
+
type MatchNode = {
|
|
22
|
+
kind: "match";
|
|
23
|
+
source: BuilderStepHandle;
|
|
24
|
+
when: (value: unknown) => boolean;
|
|
25
|
+
then: BuilderNode;
|
|
26
|
+
else?: BuilderNode;
|
|
27
|
+
};
|
|
28
|
+
type BranchNode = {
|
|
29
|
+
kind: "branch";
|
|
30
|
+
condition: (ctx: Record<string, unknown>) => boolean;
|
|
31
|
+
needs?: Record<string, BuilderStepHandle>;
|
|
32
|
+
then: BuilderNode;
|
|
33
|
+
else?: BuilderNode;
|
|
34
|
+
};
|
|
35
|
+
type WorktreeNode = {
|
|
36
|
+
kind: "worktree";
|
|
37
|
+
id?: string;
|
|
38
|
+
path: string;
|
|
39
|
+
branch?: string;
|
|
40
|
+
skipIf?: (ctx: Record<string, unknown>) => boolean;
|
|
41
|
+
needs?: Record<string, BuilderStepHandle>;
|
|
42
|
+
children: BuilderNode;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type BuilderNode =
|
|
46
|
+
| BuilderStepHandle
|
|
47
|
+
| SequenceNode
|
|
48
|
+
| ParallelNode
|
|
49
|
+
| LoopNode
|
|
50
|
+
| MatchNode
|
|
51
|
+
| BranchNode
|
|
52
|
+
| WorktreeNode;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Effect, Schema } from "effect";
|
|
2
|
+
import type { SQLiteTable } from "drizzle-orm/sqlite-core";
|
|
3
|
+
import type { CachePolicy } from "@smithers-orchestrator/scheduler/CachePolicy";
|
|
4
|
+
import type { RetryPolicy } from "@smithers-orchestrator/scheduler/RetryPolicy";
|
|
5
|
+
|
|
6
|
+
type AnySchema = Schema.Schema<unknown, unknown, never>;
|
|
7
|
+
type AnyEffect = unknown | Promise<unknown> | Effect.Effect<unknown, unknown, unknown>;
|
|
8
|
+
|
|
9
|
+
type BuilderStepContext = Record<string, unknown> & {
|
|
10
|
+
input: unknown;
|
|
11
|
+
executionId: string;
|
|
12
|
+
stepId: string;
|
|
13
|
+
attempt: number;
|
|
14
|
+
signal: AbortSignal;
|
|
15
|
+
iteration: number;
|
|
16
|
+
heartbeat: (data?: unknown) => void;
|
|
17
|
+
lastHeartbeat: unknown | null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ApprovalOptions = {
|
|
21
|
+
needs?: Record<string, BuilderStepHandle>;
|
|
22
|
+
request: (ctx: Record<string, unknown>) => {
|
|
23
|
+
title: string;
|
|
24
|
+
summary?: string | null;
|
|
25
|
+
};
|
|
26
|
+
onDeny?: "fail" | "continue" | "skip";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type BuilderStepHandle = {
|
|
30
|
+
kind: "step" | "approval";
|
|
31
|
+
id: string;
|
|
32
|
+
localId: string;
|
|
33
|
+
tableKey: string;
|
|
34
|
+
tableName: string;
|
|
35
|
+
table: SQLiteTable;
|
|
36
|
+
output: AnySchema;
|
|
37
|
+
needs: Record<string, BuilderStepHandle>;
|
|
38
|
+
run?: (ctx: BuilderStepContext) => AnyEffect;
|
|
39
|
+
request?: ApprovalOptions["request"];
|
|
40
|
+
onDeny?: "fail" | "continue" | "skip";
|
|
41
|
+
retries: number;
|
|
42
|
+
retryPolicy?: RetryPolicy;
|
|
43
|
+
timeoutMs: number | null;
|
|
44
|
+
skipIf?: (ctx: BuilderStepContext) => boolean;
|
|
45
|
+
loopId?: string;
|
|
46
|
+
cache?: CachePolicy;
|
|
47
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
|
|
2
|
+
import type { SQLiteTable } from "drizzle-orm/sqlite-core";
|
|
3
|
+
import type { TaskDescriptor } from "@smithers-orchestrator/graph/TaskDescriptor";
|
|
4
|
+
import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
|
|
5
|
+
import type { EventBus } from "../events.js";
|
|
6
|
+
import type { HijackState } from "../HijackState.ts";
|
|
7
|
+
import type { TaskBridgeToolConfig } from "./TaskBridgeToolConfig.ts";
|
|
8
|
+
|
|
9
|
+
export type LegacyExecuteTaskFn = (
|
|
10
|
+
adapter: SmithersDb,
|
|
11
|
+
db: BunSQLiteDatabase<Record<string, unknown>>,
|
|
12
|
+
runId: string,
|
|
13
|
+
desc: TaskDescriptor,
|
|
14
|
+
descriptorMap: Map<string, TaskDescriptor>,
|
|
15
|
+
inputTable: SQLiteTable,
|
|
16
|
+
eventBus: EventBus,
|
|
17
|
+
toolConfig: TaskBridgeToolConfig,
|
|
18
|
+
workflowName: string,
|
|
19
|
+
cacheEnabled: boolean,
|
|
20
|
+
signal?: AbortSignal,
|
|
21
|
+
disabledAgents?: Set<string>,
|
|
22
|
+
runAbortController?: AbortController,
|
|
23
|
+
hijackState?: HijackState,
|
|
24
|
+
) => Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RunStatusSchema } from "./RunStatusSchema.ts";
|
|
2
|
+
|
|
3
|
+
export type RunSummary = {
|
|
4
|
+
runId: string;
|
|
5
|
+
parentRunId: string | null;
|
|
6
|
+
workflowName: string;
|
|
7
|
+
workflowPath: string | null;
|
|
8
|
+
workflowHash: string | null;
|
|
9
|
+
status: RunStatusSchema;
|
|
10
|
+
createdAtMs: number;
|
|
11
|
+
startedAtMs: number | null;
|
|
12
|
+
finishedAtMs: number | null;
|
|
13
|
+
heartbeatAtMs: number | null;
|
|
14
|
+
runtimeOwnerId: string | null;
|
|
15
|
+
cancelRequestedAtMs: number | null;
|
|
16
|
+
hijackRequestedAtMs: number | null;
|
|
17
|
+
hijackTarget: string | null;
|
|
18
|
+
vcsType: string | null;
|
|
19
|
+
vcsRoot: string | null;
|
|
20
|
+
vcsRevision: string | null;
|
|
21
|
+
errorJson: string | null;
|
|
22
|
+
configJson: string | null;
|
|
23
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type TaggedWorkerError =
|
|
2
|
+
| {
|
|
3
|
+
_tag: "TaskAborted";
|
|
4
|
+
message: string;
|
|
5
|
+
details?: Record<string, unknown>;
|
|
6
|
+
name?: string;
|
|
7
|
+
}
|
|
8
|
+
| {
|
|
9
|
+
_tag: "TaskTimeout";
|
|
10
|
+
message: string;
|
|
11
|
+
nodeId: string;
|
|
12
|
+
attempt: number;
|
|
13
|
+
timeoutMs: number;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
_tag: "TaskHeartbeatTimeout";
|
|
17
|
+
message: string;
|
|
18
|
+
nodeId: string;
|
|
19
|
+
iteration: number;
|
|
20
|
+
attempt: number;
|
|
21
|
+
timeoutMs: number;
|
|
22
|
+
staleForMs: number;
|
|
23
|
+
lastHeartbeatAtMs: number;
|
|
24
|
+
}
|
|
25
|
+
| { _tag: "RunNotFound"; message: string; runId: string }
|
|
26
|
+
| {
|
|
27
|
+
_tag: "InvalidInput";
|
|
28
|
+
message: string;
|
|
29
|
+
details?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
_tag: "DbWriteFailed";
|
|
33
|
+
message: string;
|
|
34
|
+
details?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
_tag: "AgentCliError";
|
|
38
|
+
message: string;
|
|
39
|
+
details?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
_tag: "WorkflowFailed";
|
|
43
|
+
message: string;
|
|
44
|
+
details?: Record<string, unknown>;
|
|
45
|
+
status?: number;
|
|
46
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
export type WaitForEventDurableDeferredResolution = Schema.Schema.Type<
|
|
4
|
+
Schema.Struct<{
|
|
5
|
+
signalName: typeof Schema.String;
|
|
6
|
+
correlationId: Schema.NullOr<typeof Schema.String>;
|
|
7
|
+
payloadJson: typeof Schema.String;
|
|
8
|
+
seq: typeof Schema.Number;
|
|
9
|
+
receivedAtMs: typeof Schema.Number;
|
|
10
|
+
}>
|
|
11
|
+
>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type WorkerDispatchKind = "compute" | "static" | "legacy";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WorkerTaskKind } from "./WorkerTaskKind.ts";
|
|
2
|
+
import type { WorkerDispatchKind } from "./WorkerDispatchKind.ts";
|
|
3
|
+
|
|
4
|
+
export type WorkerTask = {
|
|
5
|
+
executionId: string;
|
|
6
|
+
bridgeKey: string;
|
|
7
|
+
workflowName: string;
|
|
8
|
+
runId: string;
|
|
9
|
+
nodeId: string;
|
|
10
|
+
iteration: number;
|
|
11
|
+
retries: number;
|
|
12
|
+
taskKind: WorkerTaskKind;
|
|
13
|
+
dispatchKind: WorkerDispatchKind;
|
|
14
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type WorkerTaskKind = "agent" | "compute" | "static";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type WorkflowPatchDecisions = Record<string, boolean>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("./TaskActivityRetryOptions.ts").TaskActivityRetryOptions} TaskActivityRetryOptions */
|
|
3
|
+
/** @typedef {import("./ExecuteTaskActivityOptions.ts").ExecuteTaskActivityOptions} ExecuteTaskActivityOptions */
|
|
4
|
+
/** @typedef {import("./TaskActivityContext.ts").TaskActivityContext} TaskActivityContext */
|
|
5
|
+
// @smithers-type-exports-end
|
|
6
|
+
|
|
7
|
+
import * as Activity from "@effect/workflow/Activity";
|
|
8
|
+
import { Effect, Schema } from "effect";
|
|
9
|
+
/** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} _SmithersDb */
|
|
10
|
+
/** @typedef {import("@smithers-orchestrator/graph/TaskDescriptor").TaskDescriptor} _TaskDescriptor */
|
|
11
|
+
|
|
12
|
+
const adapterNamespaces = new WeakMap();
|
|
13
|
+
const completedActivityResults = new Map();
|
|
14
|
+
let nextAdapterNamespace = 0;
|
|
15
|
+
/**
|
|
16
|
+
* @param {_SmithersDb} adapter
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
const getAdapterNamespace = (adapter) => {
|
|
20
|
+
const existing = adapterNamespaces.get(adapter);
|
|
21
|
+
if (existing) {
|
|
22
|
+
return existing;
|
|
23
|
+
}
|
|
24
|
+
const created = `adapter-${++nextAdapterNamespace}`;
|
|
25
|
+
adapterNamespaces.set(adapter, created);
|
|
26
|
+
return created;
|
|
27
|
+
};
|
|
28
|
+
export class RetriableTaskFailure extends Error {
|
|
29
|
+
nodeId;
|
|
30
|
+
attempt;
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} nodeId
|
|
33
|
+
* @param {number} attempt
|
|
34
|
+
*/
|
|
35
|
+
constructor(nodeId, attempt) {
|
|
36
|
+
super(`Task ${nodeId} failed on attempt ${attempt} and should be retried`);
|
|
37
|
+
this.name = "RetriableTaskFailure";
|
|
38
|
+
this.nodeId = nodeId;
|
|
39
|
+
this.attempt = attempt;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @param {unknown} error
|
|
44
|
+
* @returns {error is RetriableTaskFailure}
|
|
45
|
+
*/
|
|
46
|
+
const isRetriableTaskFailure = (error) => error instanceof RetriableTaskFailure;
|
|
47
|
+
/**
|
|
48
|
+
* @param {_SmithersDb} adapter
|
|
49
|
+
* @param {string} workflowName
|
|
50
|
+
* @param {string} runId
|
|
51
|
+
* @param {_TaskDescriptor} desc
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
export const makeTaskBridgeKey = (adapter, workflowName, runId, desc) => [
|
|
55
|
+
"smithers-task-bridge",
|
|
56
|
+
getAdapterNamespace(adapter),
|
|
57
|
+
workflowName,
|
|
58
|
+
runId,
|
|
59
|
+
desc.nodeId,
|
|
60
|
+
String(desc.iteration),
|
|
61
|
+
].join(":");
|
|
62
|
+
/**
|
|
63
|
+
* @param {_SmithersDb} adapter
|
|
64
|
+
* @param {string} workflowName
|
|
65
|
+
* @param {string} runId
|
|
66
|
+
* @param {_TaskDescriptor} desc
|
|
67
|
+
* @param {number} attempt
|
|
68
|
+
* @param {boolean} [includeAttempt]
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
const makeActivityIdempotencyKey = (adapter, workflowName, runId, desc, attempt, includeAttempt) => {
|
|
72
|
+
const base = makeTaskBridgeKey(adapter, workflowName, runId, desc);
|
|
73
|
+
return includeAttempt ? `${base}:attempt:${attempt}` : base;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* @template A
|
|
77
|
+
* @param {_TaskDescriptor} desc
|
|
78
|
+
* @param {(context: TaskActivityContext) => Promise<A> | A} executeFn
|
|
79
|
+
* @param {Pick<ExecuteTaskActivityOptions, "includeAttemptInIdempotencyKey">} [options]
|
|
80
|
+
*/
|
|
81
|
+
export const makeTaskActivity = (desc, executeFn, options) => Activity.make({
|
|
82
|
+
name: desc.nodeId,
|
|
83
|
+
success: Schema.Unknown,
|
|
84
|
+
error: Schema.Unknown,
|
|
85
|
+
execute: Effect.gen(function* () {
|
|
86
|
+
const attempt = yield* Activity.CurrentAttempt;
|
|
87
|
+
const idempotencyKey = yield* Activity.idempotencyKey(desc.nodeId, {
|
|
88
|
+
includeAttempt: options?.includeAttemptInIdempotencyKey,
|
|
89
|
+
});
|
|
90
|
+
return yield* Effect.tryPromise({
|
|
91
|
+
try: () => Promise.resolve(executeFn({ attempt, idempotencyKey })),
|
|
92
|
+
catch: (error) => error,
|
|
93
|
+
});
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* @template A
|
|
98
|
+
* @param {_SmithersDb} adapter
|
|
99
|
+
* @param {string} workflowName
|
|
100
|
+
* @param {string} runId
|
|
101
|
+
* @param {_TaskDescriptor} desc
|
|
102
|
+
* @param {(context: TaskActivityContext) => Promise<A> | A} executeFn
|
|
103
|
+
* @param {ExecuteTaskActivityOptions} [options]
|
|
104
|
+
* @returns {Promise<A>}
|
|
105
|
+
*/
|
|
106
|
+
export const executeTaskActivity = async (adapter, workflowName, runId, desc, executeFn, options) => {
|
|
107
|
+
const initialAttempt = Math.max(1, options?.initialAttempt ?? 1);
|
|
108
|
+
const retry = options?.retry === undefined
|
|
109
|
+
? { times: desc.retries, while: isRetriableTaskFailure }
|
|
110
|
+
: options.retry;
|
|
111
|
+
let attempt = initialAttempt;
|
|
112
|
+
while (true) {
|
|
113
|
+
const idempotencyKey = makeActivityIdempotencyKey(adapter, workflowName, runId, desc, attempt, options?.includeAttemptInIdempotencyKey);
|
|
114
|
+
if (completedActivityResults.has(idempotencyKey)) {
|
|
115
|
+
return completedActivityResults.get(idempotencyKey);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const result = await Promise.resolve(executeFn({ attempt, idempotencyKey }));
|
|
119
|
+
completedActivityResults.set(idempotencyKey, result);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (retry === false ||
|
|
124
|
+
attempt - initialAttempt >= retry.times ||
|
|
125
|
+
!(retry.while ?? isRetriableTaskFailure)(error)) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
attempt += 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { TaskAborted } from "@smithers-orchestrator/errors/TaskAborted";
|
|
2
|
+
/**
|
|
3
|
+
* @returns {TaskAborted}
|
|
4
|
+
*/
|
|
5
|
+
export function makeAbortError(message = "Task aborted") {
|
|
6
|
+
return new TaskAborted({
|
|
7
|
+
message,
|
|
8
|
+
name: "AbortError",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @param {AbortController} controller
|
|
13
|
+
* @param {AbortSignal} [signal]
|
|
14
|
+
*/
|
|
15
|
+
export function wireAbortSignal(controller, signal) {
|
|
16
|
+
if (!signal) {
|
|
17
|
+
return () => { };
|
|
18
|
+
}
|
|
19
|
+
const forwardAbort = () => {
|
|
20
|
+
controller.abort(signal.reason ?? makeAbortError());
|
|
21
|
+
};
|
|
22
|
+
if (signal.aborted) {
|
|
23
|
+
forwardAbort();
|
|
24
|
+
return () => { };
|
|
25
|
+
}
|
|
26
|
+
signal.addEventListener("abort", forwardAbort, { once: true });
|
|
27
|
+
return () => signal.removeEventListener("abort", forwardAbort);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @param {string | null} [metaJson]
|
|
31
|
+
* @returns {Record<string, unknown>}
|
|
32
|
+
*/
|
|
33
|
+
export function parseAttemptMetaJson(metaJson) {
|
|
34
|
+
if (!metaJson)
|
|
35
|
+
return {};
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(metaJson);
|
|
38
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
39
|
+
? parsed
|
|
40
|
+
: {};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|