@sshkeda/task 0.0.0-stage → 1.0.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/README.md +131 -2
- package/package.json +41 -4
- package/src/index.ts +2 -0
- package/src/lib/errors/index.ts +15 -0
- package/src/lib/errors/internal/errors.ts +147 -0
- package/src/lib/kill-leftover-processes/index.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/command-words.ts +16 -0
- package/src/lib/kill-leftover-processes/internal/find-process-ids.ts +36 -0
- package/src/lib/kill-leftover-processes/internal/kill-process-groups.ts +270 -0
- package/src/lib/kill-leftover-processes/internal/list-linux-processes-from-proc.ts +191 -0
- package/src/lib/kill-leftover-processes/internal/list-macos-processes-with-sysctl.ts +335 -0
- package/src/lib/kill-leftover-processes/internal/list-processes.ts +58 -0
- package/src/lib/kill-leftover-processes/internal/mark-run-processes.ts +367 -0
- package/src/lib/kill-leftover-processes/internal/process-cleanup-errors.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/send-signal.ts +32 -0
- package/src/lib/kill-leftover-processes/internal/signal-run-processes.ts +48 -0
- package/src/lib/kill-leftover-processes/internal/started-process-groups.ts +107 -0
- package/src/lib/main-thread/index.ts +3 -0
- package/src/lib/main-thread/internal/listen-for-stop-requests.ts +102 -0
- package/src/lib/main-thread/internal/run-main-thread.ts +81 -0
- package/src/lib/main-thread/internal/run-root-task-fiber.ts +191 -0
- package/src/lib/main-thread/internal/stops-at-first-failure.ts +20 -0
- package/src/lib/main-thread/internal/trace-the-run.ts +107 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/index.ts +7 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/follow-imports-from-entry-files.ts +306 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/load-all-reporting-first-failure.ts +11 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/make-module-graph.ts +48 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/read-configured-loaders.ts +64 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/resolve-module-specifiers.ts +60 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/rewrite-worker-imports.ts +118 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/worker-import-errors.ts +19 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/index.ts +7 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/handle-worker-events.ts +348 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/make-task-runs.ts +207 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/index.ts +1 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/await-within-counted-limit.ts +29 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/execute-task.ts +272 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/make-task-setup-clock.ts +129 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/run-requested-tasks.ts +135 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/define-task-run-state.ts +288 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/make-stopwatch.ts +39 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts +36 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/stop-task.ts +166 -0
- package/src/lib/main-thread/lib/scan-module-source/index.ts +1 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-lexical-bindings.ts +154 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-task-declarations.ts +205 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/parse-module-text.ts +34 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-literal-string.ts +15 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-module-loads.ts +316 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/scan-module-text.ts +70 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/unwrap-type-assertions.ts +10 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/visit-non-reference-identifiers.ts +21 -0
- package/src/lib/main-thread/shared/fail-run.ts +36 -0
- package/src/lib/main-thread/shared/find-failure-reason.ts +43 -0
- package/src/lib/main-thread/shared/find-package-directories.ts +34 -0
- package/src/lib/main-thread/shared/find-tasks-reachable-from-root-task-file.ts +246 -0
- package/src/lib/main-thread/shared/list-directory-paths-up-to-root.ts +13 -0
- package/src/lib/main-thread/shared/main-thread-errors.ts +373 -0
- package/src/lib/main-thread/shared/package-json-errors.ts +93 -0
- package/src/lib/main-thread/shared/provide-discovered-tasks.ts +26 -0
- package/src/lib/main-thread/shared/provide-process-groups.ts +63 -0
- package/src/lib/main-thread/shared/provide-run-root-task-config.ts +66 -0
- package/src/lib/main-thread/shared/provide-stop-state.ts +150 -0
- package/src/lib/main-thread/shared/provide-worker-pool.ts +607 -0
- package/src/lib/main-thread/shared/read-bare-package-name.ts +3 -0
- package/src/lib/main-thread/shared/read-package-json.ts +149 -0
- package/src/lib/main-thread/shared/report-task-failure.ts +31 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/index.ts +5 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/internal/load-rewritten-imports.ts +34 -0
- package/src/lib/supervise-the-run/index.ts +15 -0
- package/src/lib/supervise-the-run/internal/adopt-orphans-on-linux.ts +73 -0
- package/src/lib/supervise-the-run/internal/check-root-task-file.ts +8 -0
- package/src/lib/supervise-the-run/internal/report-ready.ts +12 -0
- package/src/lib/supervise-the-run/internal/share-process-groups.ts +25 -0
- package/src/lib/supervise-the-run/internal/start-watching-the-supervisor.ts +6 -0
- package/src/lib/supervise-the-run/internal/supervise-run.ts +155 -0
- package/src/lib/supervise-the-run/internal/talk-to-the-supervisor.ts +53 -0
- package/src/lib/supervise-the-run/internal/watch-the-supervisor.ts +61 -0
- package/src/lib/task/index.ts +5 -0
- package/src/lib/task/internal/make-tasks.ts +82 -0
- package/src/lib/task/internal/run-command.ts +194 -0
- package/src/lib/task/internal/run-root-task.ts +263 -0
- package/src/lib/task/internal/run-task-lists.ts +51 -0
- package/src/lib/task/internal/trust-output-mode-names.ts +18 -0
- package/src/lib/task/internal/trust-task-results.ts +35 -0
- package/src/lib/task/internal/use-task-services.ts +4 -0
- package/src/lib/worker-thread/index.ts +3 -0
- package/src/lib/worker-thread/internal/answer-main-thread-messages.ts +141 -0
- package/src/lib/worker-thread/internal/describe-task-body-result.ts +91 -0
- package/src/lib/worker-thread/internal/name-config-key-not-set.ts +57 -0
- package/src/lib/worker-thread/internal/run-task-body.ts +117 -0
- package/src/lib/worker-thread/internal/serve-on-the-parent-port.ts +63 -0
- package/src/lib/worker-thread/internal/start-worker-thread.ts +41 -0
- package/src/lib/worker-thread/lib/provide-task-services/index.ts +1 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/make-task-logger.ts +34 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/provide-task-services.ts +86 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/read-standard-input-from-main-thread.ts +58 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/resolve-paths-from-package-directory.ts +61 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/run-commands-in-package-directory.ts +24 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-commands-with-bun.ts +545 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-reported-process.ts +151 -0
- package/src/lib/worker-thread/shared/await-main-thread-answers.ts +76 -0
- package/src/lib/worker-thread/shared/decode-task-result-json.ts +114 -0
- package/src/lib/worker-thread/shared/define-task-body-record.ts +11 -0
- package/src/lib/worker-thread/shared/describe-issue-input.ts +52 -0
- package/src/lib/worker-thread/shared/plain-object.ts +8 -0
- package/src/lib/worker-thread/shared/provide-worker-state.ts +160 -0
- package/src/lib/worker-thread/shared/worker-thread-errors.ts +41 -0
- package/src/shared/attribute-reported-failures.ts +25 -0
- package/src/shared/claim-processes-to-report.ts +22 -0
- package/src/shared/command-errors.ts +44 -0
- package/src/shared/complete-discarded-calls-once-sent.ts +12 -0
- package/src/shared/cut-to-graphemes.ts +12 -0
- package/src/shared/environment-config-errors.ts +74 -0
- package/src/shared/error-fields.ts +13 -0
- package/src/shared/find-bun-loader-for-script.ts +32 -0
- package/src/shared/find-node-modules-package-directory.ts +35 -0
- package/src/shared/find-user-locations-in-stack-traces.ts +64 -0
- package/src/shared/format-error-lines.ts +53 -0
- package/src/shared/format-task-lines.ts +21 -0
- package/src/shared/hold-task-logs.ts +24 -0
- package/src/shared/invariant-errors.ts +28 -0
- package/src/shared/mark-task-worker-thread-contexts.ts +29 -0
- package/src/shared/module-graph-errors.ts +49 -0
- package/src/shared/name-config-key.ts +42 -0
- package/src/shared/names-from-package-json.ts +7 -0
- package/src/shared/print-to-standard-error.ts +48 -0
- package/src/shared/process-errors.ts +191 -0
- package/src/shared/read-environment-config.ts +239 -0
- package/src/shared/record-tasks-made.ts +10 -0
- package/src/shared/report-cause.ts +140 -0
- package/src/shared/report-errors.ts +121 -0
- package/src/shared/resolve-task-options.ts +20 -0
- package/src/shared/rpc.ts +161 -0
- package/src/shared/run-in-child-fiber-and-join.ts +16 -0
- package/src/shared/run-marker.ts +7 -0
- package/src/shared/save-standard-stream-flags.ts +57 -0
- package/src/shared/select-cause-reasons.ts +14 -0
- package/src/shared/signal-run-called-twice.ts +11 -0
- package/src/shared/standard-input-streams.ts +41 -0
- package/src/shared/stop-signals.ts +41 -0
- package/src/shared/task-errors.ts +224 -0
- package/src/shared/task-package.ts +5 -0
- package/src/shared/task-results.ts +55 -0
- package/src/shared/task-services.ts +30 -0
- package/src/shared/write-trace-file.ts +112 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Duration } from "effect";
|
|
2
|
+
|
|
3
|
+
export const TASK_OPTION_NAMES = [
|
|
4
|
+
"timeout",
|
|
5
|
+
"cleanup",
|
|
6
|
+
] as const satisfies readonly (keyof Parameters<typeof resolveTaskOptions>[0])[];
|
|
7
|
+
|
|
8
|
+
export const resolveTaskOptions = (options: {
|
|
9
|
+
readonly timeout?: Duration.Input;
|
|
10
|
+
readonly cleanup?: Duration.Input;
|
|
11
|
+
}) =>
|
|
12
|
+
Object.freeze({
|
|
13
|
+
timeout: options.timeout ?? null,
|
|
14
|
+
cleanup: options.cleanup ?? null,
|
|
15
|
+
unknownOptionNames: Object.freeze(
|
|
16
|
+
Object.keys(options).filter(
|
|
17
|
+
(optionName) => !TASK_OPTION_NAMES.some((knownName) => knownName === optionName),
|
|
18
|
+
),
|
|
19
|
+
),
|
|
20
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { LogLevel, Schema } from "effect";
|
|
2
|
+
import { Rpc, RpcGroup } from "effect/rpc";
|
|
3
|
+
import { Transferable } from "effect/workers";
|
|
4
|
+
|
|
5
|
+
import { PrintableError } from "../lib/errors/index.ts";
|
|
6
|
+
import { ProcessIdentity } from "./claim-processes-to-report.ts";
|
|
7
|
+
import { SCRIPT_LOADERS } from "./find-bun-loader-for-script.ts";
|
|
8
|
+
import { RunMarker } from "./run-marker.ts";
|
|
9
|
+
import { AwaitedTasksFailure, TaskRequestFailure } from "./task-errors.ts";
|
|
10
|
+
|
|
11
|
+
export const StandardInputRead = Schema.TaggedUnion({
|
|
12
|
+
Chunks: { chunks: Schema.NonEmptyArray(Schema.Uint8Array) },
|
|
13
|
+
Ended: {},
|
|
14
|
+
Failed: { reason: Schema.String },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const StartTaskFiber = Schema.Struct({
|
|
18
|
+
taskName: Schema.String,
|
|
19
|
+
taskFilePath: Schema.String,
|
|
20
|
+
taskPackageDirectoryPath: Schema.String,
|
|
21
|
+
cleanupMilliseconds: Schema.Finite,
|
|
22
|
+
traceId: Schema.String,
|
|
23
|
+
spanId: Schema.String,
|
|
24
|
+
standardInput: Schema.Struct({ isTerminal: Schema.Boolean, rootTaskName: Schema.String }),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const TaskFiberResult = Schema.TaggedUnion({
|
|
28
|
+
Returned: {
|
|
29
|
+
value: Schema.optional(Schema.Json),
|
|
30
|
+
elapsedMilliseconds: Schema.Finite,
|
|
31
|
+
},
|
|
32
|
+
Failed: {
|
|
33
|
+
failures: Schema.NonEmptyArray(PrintableError),
|
|
34
|
+
details: Schema.optional(Schema.String),
|
|
35
|
+
failedCommandExitCode: Schema.optional(Schema.Int),
|
|
36
|
+
},
|
|
37
|
+
AwaitedTasksFailed: { failure: AwaitedTasksFailure },
|
|
38
|
+
CleanupFailed: {
|
|
39
|
+
failures: Schema.NonEmptyArray(PrintableError),
|
|
40
|
+
details: Schema.optional(Schema.String),
|
|
41
|
+
},
|
|
42
|
+
Interrupted: {},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const TaskFiberFailure = Schema.Union([
|
|
46
|
+
TaskFiberResult.cases.Failed,
|
|
47
|
+
TaskFiberResult.cases.AwaitedTasksFailed,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
export const StandardErrorLine = Schema.Struct({
|
|
51
|
+
logLevel: Schema.Literals(LogLevel.values),
|
|
52
|
+
text: Schema.String,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const TASK_EVENT_FIELDS = {
|
|
56
|
+
TraceWriteFailed: { reason: Schema.String },
|
|
57
|
+
StandardErrorLineRequested: StandardErrorLine.fields,
|
|
58
|
+
TaskFiberLoading: { taskName: Schema.String },
|
|
59
|
+
TaskFiberStarted: { taskName: Schema.String },
|
|
60
|
+
TaskFiberReceivedAwaitedFailure: { taskName: Schema.String, awaitedTaskName: Schema.String },
|
|
61
|
+
TaskFiberFailedBeforeCleanup: { taskName: Schema.String, failure: TaskFiberFailure },
|
|
62
|
+
TaskFiberProcessLeaderExited: {
|
|
63
|
+
taskName: Schema.String,
|
|
64
|
+
processId: Schema.Int,
|
|
65
|
+
latestMemberStartTime: Schema.NullOr(Schema.String),
|
|
66
|
+
},
|
|
67
|
+
TaskFiberServed: { taskName: Schema.String, value: Schema.optional(Schema.Json) },
|
|
68
|
+
TaskFiberStartedCleanUp: { taskName: Schema.String },
|
|
69
|
+
TaskFiberEnded: {
|
|
70
|
+
taskName: Schema.String,
|
|
71
|
+
result: TaskFiberResult,
|
|
72
|
+
logs: Schema.Array(Schema.String),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const TaskEvent = Schema.TaggedUnion(TASK_EVENT_FIELDS);
|
|
77
|
+
|
|
78
|
+
export const StartedProcess = Schema.Struct({
|
|
79
|
+
taskName: Schema.String,
|
|
80
|
+
processId: Schema.Int,
|
|
81
|
+
startTime: Schema.NullOr(Schema.String),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const ProcessReportsToClaim = Schema.Struct({
|
|
85
|
+
taskName: Schema.String,
|
|
86
|
+
processes: Schema.Array(ProcessIdentity),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const TaskWorkerRpcs = RpcGroup.make(
|
|
90
|
+
Rpc.make("StartTaskFiber", { payload: StartTaskFiber }),
|
|
91
|
+
Rpc.make("StopTaskFiber", { payload: { taskName: Schema.String } }),
|
|
92
|
+
Rpc.make("AnswerIfResponsive"),
|
|
93
|
+
Rpc.make("ExitWorker"),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
export const MainThreadRpcs = RpcGroup.make(
|
|
97
|
+
Rpc.make("ReportTaskEvent", { payload: TaskEvent }),
|
|
98
|
+
Rpc.make("RequestTask", {
|
|
99
|
+
payload: { taskName: Schema.String, dependencyTaskName: Schema.String },
|
|
100
|
+
success: Schema.Struct({ value: Schema.optionalKey(Schema.Json) }),
|
|
101
|
+
error: TaskRequestFailure,
|
|
102
|
+
}),
|
|
103
|
+
Rpc.make("ReadStandardInput", {
|
|
104
|
+
payload: { taskName: Schema.String },
|
|
105
|
+
success: StandardInputRead,
|
|
106
|
+
}),
|
|
107
|
+
Rpc.make("RecordStartedProcess", { payload: StartedProcess }),
|
|
108
|
+
Rpc.make("ClaimProcessReports", {
|
|
109
|
+
payload: ProcessReportsToClaim,
|
|
110
|
+
success: Schema.Array(ProcessIdentity),
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export const WorkerInitialMessage = Schema.Struct({ mainThreadPort: Transferable.MessagePort });
|
|
115
|
+
|
|
116
|
+
const RequestIdentifier = Schema.Union([Schema.String, Schema.Finite]);
|
|
117
|
+
|
|
118
|
+
export const MessageFromMainThreadRpcClient = Schema.Union([
|
|
119
|
+
Schema.TaggedStruct("Request", {
|
|
120
|
+
id: RequestIdentifier,
|
|
121
|
+
tag: Schema.String,
|
|
122
|
+
payload: Schema.Unknown,
|
|
123
|
+
headers: Schema.Array(Schema.mutable(Schema.Tuple([Schema.String, Schema.String]))),
|
|
124
|
+
}),
|
|
125
|
+
Schema.TaggedStruct("Ack", { requestId: RequestIdentifier }),
|
|
126
|
+
Schema.TaggedStruct("Interrupt", { requestId: RequestIdentifier }),
|
|
127
|
+
Schema.TaggedStruct("Eof", {}),
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
export const MessageFromMainThreadRpcServer = Schema.Union([
|
|
131
|
+
Schema.TaggedStruct("Exit", {
|
|
132
|
+
requestId: RequestIdentifier,
|
|
133
|
+
exit: Schema.Union([
|
|
134
|
+
Schema.TaggedStruct("Success", { value: Schema.Unknown }),
|
|
135
|
+
Schema.TaggedStruct("Failure", {
|
|
136
|
+
cause: Schema.Array(
|
|
137
|
+
Schema.Union([
|
|
138
|
+
Schema.TaggedStruct("Fail", { error: Schema.Unknown }),
|
|
139
|
+
Schema.TaggedStruct("Die", { defect: Schema.Unknown }),
|
|
140
|
+
Schema.TaggedStruct("Interrupt", {
|
|
141
|
+
fiberId: Schema.UndefinedOr(Schema.NullOr(Schema.Finite)),
|
|
142
|
+
}),
|
|
143
|
+
]),
|
|
144
|
+
),
|
|
145
|
+
}),
|
|
146
|
+
]),
|
|
147
|
+
}),
|
|
148
|
+
Schema.TaggedStruct("Defect", { defect: Schema.Unknown }),
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
export const WorkerData = Schema.Struct({
|
|
152
|
+
workerId: Schema.Int,
|
|
153
|
+
traceFilePath: Schema.String,
|
|
154
|
+
projectDirectoryPath: Schema.String,
|
|
155
|
+
rewrittenFiles: Schema.Record(
|
|
156
|
+
Schema.String,
|
|
157
|
+
Schema.Struct({ contents: Schema.String, loader: Schema.Literals(SCRIPT_LOADERS) }),
|
|
158
|
+
),
|
|
159
|
+
taskLogHold: Schema.instanceOf(Int32Array<SharedArrayBuffer>),
|
|
160
|
+
runMarker: Schema.NullOr(RunMarker),
|
|
161
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Effect, Fiber } from "effect";
|
|
2
|
+
|
|
3
|
+
export const runInChildFiberAndJoin = <Success, Failure, Services>(
|
|
4
|
+
effect: Effect.Effect<Success, Failure, Services>,
|
|
5
|
+
) =>
|
|
6
|
+
Effect.uninterruptibleMask((restore) =>
|
|
7
|
+
Effect.flatMap(
|
|
8
|
+
Effect.forkChild(restore(effect), { startImmediately: true, uninterruptible: true }),
|
|
9
|
+
(childFiber) =>
|
|
10
|
+
Effect.interruptible(Fiber.join(childFiber)).pipe(
|
|
11
|
+
Effect.onInterrupt(() =>
|
|
12
|
+
Effect.andThen(Fiber.interrupt(childFiber), Fiber.join(childFiber)),
|
|
13
|
+
),
|
|
14
|
+
),
|
|
15
|
+
),
|
|
16
|
+
);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { dlopen, FFIType } from "bun:ffi";
|
|
2
|
+
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
|
|
5
|
+
const F_GETFL = 3;
|
|
6
|
+
const F_SETFL = 4;
|
|
7
|
+
const STANDARD_INPUT_FILE_DESCRIPTOR = 0;
|
|
8
|
+
const STANDARD_OUTPUT_FILE_DESCRIPTOR = 1;
|
|
9
|
+
const STANDARD_ERROR_FILE_DESCRIPTOR = 2;
|
|
10
|
+
const STANDARD_STREAM_FILE_DESCRIPTORS = [
|
|
11
|
+
STANDARD_INPUT_FILE_DESCRIPTOR,
|
|
12
|
+
STANDARD_OUTPUT_FILE_DESCRIPTOR,
|
|
13
|
+
STANDARD_ERROR_FILE_DESCRIPTOR,
|
|
14
|
+
] as const;
|
|
15
|
+
const FILE_CONTROL = {
|
|
16
|
+
args: [FFIType.i32, FFIType.i32, FFIType.i32],
|
|
17
|
+
returns: FFIType.i32,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
const loadFileControl = () => {
|
|
21
|
+
if (process.platform === "darwin") {
|
|
22
|
+
const library = dlopen("/usr/lib/libSystem.B.dylib", { __fcntl: FILE_CONTROL });
|
|
23
|
+
return {
|
|
24
|
+
fileControl: library.symbols.__fcntl,
|
|
25
|
+
close: () => {
|
|
26
|
+
library.close();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const library = dlopen("libc.so.6", { fcntl: FILE_CONTROL });
|
|
31
|
+
return {
|
|
32
|
+
fileControl: library.symbols.fcntl,
|
|
33
|
+
close: () => {
|
|
34
|
+
library.close();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const saveStandardStreamFlags = Effect.acquireRelease(
|
|
40
|
+
Effect.sync(loadFileControl),
|
|
41
|
+
(library) =>
|
|
42
|
+
Effect.sync(() => {
|
|
43
|
+
library.close();
|
|
44
|
+
}),
|
|
45
|
+
).pipe(
|
|
46
|
+
Effect.map(({ fileControl }) => {
|
|
47
|
+
const savedFlags = STANDARD_STREAM_FILE_DESCRIPTORS.map((fileDescriptor) => ({
|
|
48
|
+
fileDescriptor,
|
|
49
|
+
flags: fileControl(fileDescriptor, F_GETFL, 0),
|
|
50
|
+
}));
|
|
51
|
+
return Effect.sync(() => {
|
|
52
|
+
for (const { fileDescriptor, flags } of savedFlags) {
|
|
53
|
+
fileControl(fileDescriptor, F_SETFL, flags);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Cause } from "effect";
|
|
2
|
+
|
|
3
|
+
export const failuresOfCause = <Failure>(cause: Cause.Cause<Failure>) =>
|
|
4
|
+
Cause.fromReasons(cause.reasons.filter((reason) => !Cause.isInterruptReason(reason)));
|
|
5
|
+
|
|
6
|
+
export const failureValuesOfReason = <Failure>(reason: Cause.Reason<Failure>) => {
|
|
7
|
+
if (Cause.isFailReason(reason)) {
|
|
8
|
+
return [reason.error];
|
|
9
|
+
}
|
|
10
|
+
return Cause.isDieReason(reason) ? [reason.defect] : [];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const failureValuesOfCause = <Failure>(cause: Cause.Cause<Failure>) =>
|
|
14
|
+
cause.reasons.flatMap(failureValuesOfReason);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Deferred } from "effect";
|
|
2
|
+
|
|
3
|
+
const makeRunCallDeferred = () =>
|
|
4
|
+
Deferred.makeUnsafe<{
|
|
5
|
+
readonly rootTaskName: string;
|
|
6
|
+
readonly stack: string | undefined;
|
|
7
|
+
}>();
|
|
8
|
+
|
|
9
|
+
export const runningRootTaskDeferred = makeRunCallDeferred();
|
|
10
|
+
|
|
11
|
+
export const laterRunCallDeferred = makeRunCallDeferred();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Cause, Deferred, Effect, Exit } from "effect";
|
|
2
|
+
import type { PlatformError } from "effect";
|
|
3
|
+
|
|
4
|
+
import { runInChildFiberAndJoin } from "./run-in-child-fiber-and-join.ts";
|
|
5
|
+
|
|
6
|
+
const failuresOfStandardInputClosedByCommand = new WeakSet<PlatformError.PlatformError>();
|
|
7
|
+
|
|
8
|
+
export const markStandardInputClosedByCommand = (failure: PlatformError.PlatformError) => {
|
|
9
|
+
failuresOfStandardInputClosedByCommand.add(failure);
|
|
10
|
+
return failure;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const isStandardInputClosedByCommand = (failure: PlatformError.PlatformError) =>
|
|
14
|
+
failuresOfStandardInputClosedByCommand.has(failure);
|
|
15
|
+
|
|
16
|
+
export const makeStandardInputRunner = () => {
|
|
17
|
+
const interruptedItselfDeferred = Deferred.makeUnsafe<void>();
|
|
18
|
+
return {
|
|
19
|
+
run: <Success, Failure, Services>(
|
|
20
|
+
standardInputRun: Effect.Effect<Success, Failure, Services>,
|
|
21
|
+
) =>
|
|
22
|
+
Effect.withFiber((runningFiber) =>
|
|
23
|
+
runInChildFiberAndJoin(
|
|
24
|
+
Effect.onExit(standardInputRun, (standardInputExit) =>
|
|
25
|
+
Effect.sync(() => {
|
|
26
|
+
const interruptedByItsOwnFibers =
|
|
27
|
+
Exit.isFailure(standardInputExit) &&
|
|
28
|
+
Cause.hasInterruptsOnly(standardInputExit.cause) &&
|
|
29
|
+
standardInputExit.cause.reasons.some(
|
|
30
|
+
(reason) => Cause.isInterruptReason(reason) && reason.fiberId !== runningFiber.id,
|
|
31
|
+
);
|
|
32
|
+
if (interruptedByItsOwnFibers) {
|
|
33
|
+
Deferred.doneUnsafe(interruptedItselfDeferred, Exit.void);
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
interruptedItself: () => Deferred.isDoneUnsafe(interruptedItselfDeferred),
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Duration, Effect, Queue, Record } from "effect";
|
|
2
|
+
|
|
3
|
+
export const STOP_SIGNAL_NUMBERS = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 } as const;
|
|
4
|
+
|
|
5
|
+
export const STOP_SIGNALS = Record.keys(STOP_SIGNAL_NUMBERS);
|
|
6
|
+
|
|
7
|
+
export const STOP_GRACE_PERIOD = Duration.fromInputUnsafe("5 seconds");
|
|
8
|
+
|
|
9
|
+
export const STOP_REASON_NUMBERS = { ...STOP_SIGNAL_NUMBERS, SIGPIPE: 13 } as const;
|
|
10
|
+
|
|
11
|
+
export const STOP_REASONS = Record.keys(STOP_REASON_NUMBERS);
|
|
12
|
+
|
|
13
|
+
const SHELL_EXIT_CODE_BASE_FOR_SIGNALS = 128;
|
|
14
|
+
|
|
15
|
+
export const exitCodeAfterStopSignal = (signal: (typeof STOP_REASONS)[number]) =>
|
|
16
|
+
SHELL_EXIT_CODE_BASE_FOR_SIGNALS + STOP_REASON_NUMBERS[signal];
|
|
17
|
+
|
|
18
|
+
export const listenToSignals = <Signal extends NodeJS.Signals>(signals: readonly Signal[]) =>
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const received = yield* Queue.unbounded<Signal>();
|
|
21
|
+
yield* Effect.acquireRelease(
|
|
22
|
+
Effect.sync(() =>
|
|
23
|
+
signals.map((signal) => {
|
|
24
|
+
const offer = () => {
|
|
25
|
+
Queue.offerUnsafe(received, signal);
|
|
26
|
+
};
|
|
27
|
+
process.on(signal, offer);
|
|
28
|
+
return () => {
|
|
29
|
+
process.off(signal, offer);
|
|
30
|
+
};
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
(removeListeners) =>
|
|
34
|
+
Effect.sync(() => {
|
|
35
|
+
for (const remove of removeListeners) {
|
|
36
|
+
remove();
|
|
37
|
+
}
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
return received;
|
|
41
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Duration, Effect, Option, Runtime, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AwaitedTaskFailed,
|
|
5
|
+
REPORTED_FAILURE_FIELDS,
|
|
6
|
+
SettledTasksFailed,
|
|
7
|
+
TaskNotMadeAtTopLevel,
|
|
8
|
+
TaskWaitsOnItself,
|
|
9
|
+
} from "../lib/errors/index.ts";
|
|
10
|
+
import { attributeFailures, joinMessages } from "./attribute-reported-failures.ts";
|
|
11
|
+
import { TASK_ERROR_FIELDS, UNATTRIBUTED_FAILURE_FIELDS } from "./error-fields.ts";
|
|
12
|
+
import {
|
|
13
|
+
findConstructingLocation,
|
|
14
|
+
findLocationInFile,
|
|
15
|
+
findUserLocationInStack,
|
|
16
|
+
} from "./find-user-locations-in-stack-traces.ts";
|
|
17
|
+
import { relativeToWorkingDirectory } from "./format-error-lines.ts";
|
|
18
|
+
import { ErrorStack, describeCauseMessage } from "./report-errors.ts";
|
|
19
|
+
import { TASK_PACKAGE } from "./task-package.ts";
|
|
20
|
+
|
|
21
|
+
export class TaskModuleImportFailed extends Schema.TaggedError<TaskModuleImportFailed>()(
|
|
22
|
+
"TaskModuleImportFailed",
|
|
23
|
+
{ ...TASK_ERROR_FIELDS, filePath: Schema.String, cause: Schema.Defect() },
|
|
24
|
+
) {
|
|
25
|
+
public get location() {
|
|
26
|
+
return Option.match(Schema.decodeUnknownOption(ErrorStack)(this.cause), {
|
|
27
|
+
onSome: (withStack) =>
|
|
28
|
+
findLocationInFile(withStack.stack, this.filePath) ??
|
|
29
|
+
findUserLocationInStack(withStack.stack) ??
|
|
30
|
+
this.filePath,
|
|
31
|
+
onNone: () => this.filePath,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
public override get message() {
|
|
35
|
+
return `could not import task module because ${describeCauseMessage(this.cause)}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class TaskTimedOut extends Schema.TaggedError<TaskTimedOut>()("TaskTimedOut", {
|
|
40
|
+
...TASK_ERROR_FIELDS,
|
|
41
|
+
taskName: Schema.String,
|
|
42
|
+
timeoutMilliseconds: Schema.Finite,
|
|
43
|
+
}) {
|
|
44
|
+
public override get message() {
|
|
45
|
+
return `timed out after ${Duration.format(Duration.millis(this.timeoutMilliseconds))}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class TaskSetupTimedOut extends Schema.TaggedError<TaskSetupTimedOut>()(
|
|
50
|
+
"TaskSetupTimedOut",
|
|
51
|
+
{
|
|
52
|
+
...TASK_ERROR_FIELDS,
|
|
53
|
+
taskName: Schema.String,
|
|
54
|
+
limitMilliseconds: Schema.Finite,
|
|
55
|
+
unfinishedStep: Schema.String,
|
|
56
|
+
},
|
|
57
|
+
) {
|
|
58
|
+
public override get message() {
|
|
59
|
+
return `setup timed out after ${Duration.format(Duration.millis(this.limitMilliseconds))} while ${this.unfinishedStep}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class StandardInputReadOutsideRootTask extends Schema.TaggedError<StandardInputReadOutsideRootTask>()(
|
|
64
|
+
"StandardInputReadOutsideRootTask",
|
|
65
|
+
{ ...TASK_ERROR_FIELDS, taskName: Schema.String, rootTaskName: Schema.String },
|
|
66
|
+
) {
|
|
67
|
+
public override get message() {
|
|
68
|
+
return `read standard input, which only the root task "${this.rootTaskName}" can read`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class ConfigKeyNotSet extends Schema.TaggedError<ConfigKeyNotSet>()("ConfigKeyNotSet", {
|
|
73
|
+
...TASK_ERROR_FIELDS,
|
|
74
|
+
configKey: Schema.String,
|
|
75
|
+
environmentConfigVariable: Schema.NullOr(Schema.String),
|
|
76
|
+
}) {
|
|
77
|
+
public override get message() {
|
|
78
|
+
return `configuration key ${this.configKey} is not set${this.environmentConfigVariable === null ? "" : `: the environment has no ${this.environmentConfigVariable}`}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class TaskBodyFailed extends Schema.TaggedError<TaskBodyFailed>()("TaskBodyFailed", {
|
|
83
|
+
...REPORTED_FAILURE_FIELDS,
|
|
84
|
+
...TASK_ERROR_FIELDS,
|
|
85
|
+
failedCommandExitCode: Schema.optional(Schema.Int),
|
|
86
|
+
}) {
|
|
87
|
+
public get reportedErrors() {
|
|
88
|
+
return attributeFailures(this, (failureMessage) => `its body failed with ${failureMessage}`);
|
|
89
|
+
}
|
|
90
|
+
public override get message() {
|
|
91
|
+
return joinMessages(this.reportedErrors);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class ServedTaskFailed extends Schema.TaggedError<ServedTaskFailed>()("ServedTaskFailed", {
|
|
96
|
+
...REPORTED_FAILURE_FIELDS,
|
|
97
|
+
...TASK_ERROR_FIELDS,
|
|
98
|
+
}) {
|
|
99
|
+
public get reportedErrors() {
|
|
100
|
+
return attributeFailures(
|
|
101
|
+
this,
|
|
102
|
+
(failureMessage) => `its body failed after serving with ${failureMessage}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
public override get message() {
|
|
106
|
+
return joinMessages(this.reportedErrors);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class Exited extends Schema.TaggedError<Exited>()("Exited", { code: Schema.Int }) {
|
|
111
|
+
public override get [Runtime.errorExitCode]() {
|
|
112
|
+
return this.code;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public override get [Runtime.errorReported]() {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class RunCalledTwice extends Schema.TaggedError<RunCalledTwice>()("RunCalledTwice", {
|
|
121
|
+
...TASK_ERROR_FIELDS,
|
|
122
|
+
...UNATTRIBUTED_FAILURE_FIELDS,
|
|
123
|
+
runningRootTaskName: Schema.String,
|
|
124
|
+
runningRootTaskLocation: Schema.UndefinedOr(Schema.String),
|
|
125
|
+
laterRootTaskName: Schema.String,
|
|
126
|
+
location: Schema.UndefinedOr(Schema.String).pipe(
|
|
127
|
+
Schema.withConstructorDefault(Effect.sync(findConstructingLocation)),
|
|
128
|
+
),
|
|
129
|
+
}) {
|
|
130
|
+
public override get message() {
|
|
131
|
+
const startedAt =
|
|
132
|
+
this.runningRootTaskLocation === undefined
|
|
133
|
+
? ""
|
|
134
|
+
: `, started at ${relativeToWorkingDirectory(this.runningRootTaskLocation)}`;
|
|
135
|
+
return `Task.main() was called again with "${this.laterRootTaskName}" while it runs "${this.runningRootTaskName}"${startedAt}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export class RunCalledDuringImport extends Schema.TaggedError<RunCalledDuringImport>()(
|
|
140
|
+
"RunCalledDuringImport",
|
|
141
|
+
{
|
|
142
|
+
...TASK_ERROR_FIELDS,
|
|
143
|
+
...UNATTRIBUTED_FAILURE_FIELDS,
|
|
144
|
+
location: Schema.UndefinedOr(Schema.String).pipe(
|
|
145
|
+
Schema.withConstructorDefault(Effect.sync(findConstructingLocation)),
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
) {
|
|
149
|
+
public override get message() {
|
|
150
|
+
return "Task.main() ran while Task imported this file";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class RunCalledInTaskBody extends Schema.TaggedError<RunCalledInTaskBody>()(
|
|
155
|
+
"RunCalledInTaskBody",
|
|
156
|
+
{
|
|
157
|
+
...TASK_ERROR_FIELDS,
|
|
158
|
+
rootTaskName: Schema.String,
|
|
159
|
+
location: Schema.UndefinedOr(Schema.String).pipe(
|
|
160
|
+
Schema.withConstructorDefault(Effect.sync(findConstructingLocation)),
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
) {
|
|
164
|
+
public override get message() {
|
|
165
|
+
return `Task.main() was called with "${this.rootTaskName}" inside a task body`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const TaskRequestRefusal = Schema.Union([TaskWaitsOnItself, TaskNotMadeAtTopLevel]);
|
|
170
|
+
|
|
171
|
+
export const TaskRequestFailure = Schema.Union([AwaitedTaskFailed, TaskRequestRefusal]);
|
|
172
|
+
|
|
173
|
+
export class TaskCleanupFailed extends Schema.TaggedError<TaskCleanupFailed>()(
|
|
174
|
+
"TaskCleanupFailed",
|
|
175
|
+
{ ...REPORTED_FAILURE_FIELDS, ...TASK_ERROR_FIELDS },
|
|
176
|
+
) {
|
|
177
|
+
public get reportedErrors() {
|
|
178
|
+
return attributeFailures(this, (failureMessage) => `its cleanup failed with ${failureMessage}`);
|
|
179
|
+
}
|
|
180
|
+
public override get message() {
|
|
181
|
+
return joinMessages(this.reportedErrors);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const isAwaitedTaskFailed = Schema.is(AwaitedTaskFailed);
|
|
186
|
+
|
|
187
|
+
export const AwaitedTasksFailure = Schema.Union([AwaitedTaskFailed, SettledTasksFailed]);
|
|
188
|
+
|
|
189
|
+
export const isAwaitedTasksFailure = Schema.is(AwaitedTasksFailure);
|
|
190
|
+
|
|
191
|
+
export class RootTaskFileNotChecked extends Schema.TaggedError<RootTaskFileNotChecked>()(
|
|
192
|
+
"RootTaskFileNotChecked",
|
|
193
|
+
{
|
|
194
|
+
...TASK_ERROR_FIELDS,
|
|
195
|
+
...UNATTRIBUTED_FAILURE_FIELDS,
|
|
196
|
+
filePath: Schema.String,
|
|
197
|
+
cause: Schema.Defect(),
|
|
198
|
+
},
|
|
199
|
+
) {
|
|
200
|
+
public get location() {
|
|
201
|
+
return this.filePath;
|
|
202
|
+
}
|
|
203
|
+
public override get message() {
|
|
204
|
+
return `could not check whether the root task file exists because ${describeCauseMessage(this.cause)}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export class RunCalledInStandaloneExecutable extends Schema.TaggedError<RunCalledInStandaloneExecutable>()(
|
|
209
|
+
"RunCalledInStandaloneExecutable",
|
|
210
|
+
{ ...TASK_ERROR_FIELDS, ...UNATTRIBUTED_FAILURE_FIELDS, rootTaskName: Schema.String },
|
|
211
|
+
) {
|
|
212
|
+
public override get message() {
|
|
213
|
+
return `Task.main() was called with "${this.rootTaskName}" inside a bun build --compile binary, which holds only its bundle, not the task source files ${TASK_PACKAGE.name} scans and re-imports in worker threads`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export class RunCalledInBunWatchMode extends Schema.TaggedError<RunCalledInBunWatchMode>()(
|
|
218
|
+
"RunCalledInBunWatchMode",
|
|
219
|
+
{ ...TASK_ERROR_FIELDS, ...UNATTRIBUTED_FAILURE_FIELDS, flag: Schema.String },
|
|
220
|
+
) {
|
|
221
|
+
public override get message() {
|
|
222
|
+
return `${TASK_PACKAGE.name} doesn't run under bun ${this.flag}`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Array as Arrays, Effect, Predicate, Redacted, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
export const REDACTED_TASK_RESULT_KEY = "~@sshkeda/task/Redacted";
|
|
4
|
+
|
|
5
|
+
export const TaskResult = Schema.Tree(
|
|
6
|
+
Schema.Union([
|
|
7
|
+
Schema.Null,
|
|
8
|
+
Schema.Finite,
|
|
9
|
+
Schema.Boolean,
|
|
10
|
+
Schema.String,
|
|
11
|
+
Schema.Redacted(Schema.String),
|
|
12
|
+
]),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const holdsRedactedTaskResultKey = (root: Schema.JsonArray | Schema.JsonObject) => {
|
|
16
|
+
const pendingBranches = [root];
|
|
17
|
+
for (const branch of pendingBranches) {
|
|
18
|
+
if (Object.hasOwn(branch, REDACTED_TASK_RESULT_KEY)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const children = Arrays.isArray<Schema.JsonArray | Schema.JsonObject>(branch)
|
|
22
|
+
? branch
|
|
23
|
+
: Object.values(branch);
|
|
24
|
+
for (const child of children) {
|
|
25
|
+
const isBranch = child !== null && typeof child === "object";
|
|
26
|
+
if (isBranch) {
|
|
27
|
+
pendingBranches.push(child);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const reviveRedactedTaskResults = (
|
|
35
|
+
value: Schema.Json,
|
|
36
|
+
): Effect.Effect<typeof TaskResult.Type> => {
|
|
37
|
+
const isLeaf = value === null || typeof value !== "object";
|
|
38
|
+
if (isLeaf) {
|
|
39
|
+
return Effect.succeed(value);
|
|
40
|
+
}
|
|
41
|
+
return Effect.gen(function* () {
|
|
42
|
+
if (Arrays.isArray<Schema.JsonArray | Schema.JsonObject>(value)) {
|
|
43
|
+
return yield* Effect.forEach(value, reviveRedactedTaskResults);
|
|
44
|
+
}
|
|
45
|
+
const tagged = value[REDACTED_TASK_RESULT_KEY];
|
|
46
|
+
if (Predicate.isString(tagged)) {
|
|
47
|
+
return Redacted.make(tagged);
|
|
48
|
+
}
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
yield* Effect.forEach(Object.entries(value), ([key, child]) =>
|
|
51
|
+
Effect.map(reviveRedactedTaskResults(child), (revived) => [key, revived] as const),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
import type { Effect, Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
import type { TaskResultNotJson } from "../lib/errors/index.ts";
|
|
5
|
+
import type { Task } from "../lib/task/index.ts";
|
|
6
|
+
import type { TaskRequestFailure } from "./task-errors.ts";
|
|
7
|
+
import { TASK_PACKAGE } from "./task-package.ts";
|
|
8
|
+
|
|
9
|
+
export class Tasks extends Context.Service<
|
|
10
|
+
Tasks,
|
|
11
|
+
{
|
|
12
|
+
readonly requestResult: (
|
|
13
|
+
task: Task,
|
|
14
|
+
) => Effect.Effect<Schema.Json | undefined, typeof TaskRequestFailure.Type>;
|
|
15
|
+
}
|
|
16
|
+
>()(`${TASK_PACKAGE.name}/Tasks`) {}
|
|
17
|
+
|
|
18
|
+
export class Served<Success> {
|
|
19
|
+
protected readonly servedValue: Success | undefined = undefined;
|
|
20
|
+
private constructor() {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Serving extends Context.Service<
|
|
24
|
+
Serving,
|
|
25
|
+
{
|
|
26
|
+
readonly serve: <Success>(
|
|
27
|
+
servedValue: Success,
|
|
28
|
+
) => Effect.Effect<Served<Success>, TaskResultNotJson>;
|
|
29
|
+
}
|
|
30
|
+
>()(`${TASK_PACKAGE.name}/Serving`) {}
|