UNPKG

@firfi/quint-connect

Version:

Model-based testing framework connecting Quint specifications to TypeScript implementations

60 lines 3.1 kB
import { Effect, Option, Schema } from "effect"; class ProcessStartError extends Schema.TaggedError()("ProcessStartError", { message: Schema.String, code: Schema.optional(Schema.String) }) { } // Intentional TypeScript difference: Rust preserves std::io::Error through anyhow // context. Node event payloads are an untrusted JS boundary, so retain the comparable // message/OS code without leaking `unknown` into the Effect error channel. // https://github.com/informalsystems/quint-connect/blob/4f018f54fc7dd4cef341d10111427bab59d3b307/connect/src/trace/generator/mod.rs#L23-L30 const ProcessError = Schema.Struct({ message: Schema.String, code: Schema.optional(Schema.String) }); const processStartError = (cause) => { const decoded = Schema.decodeUnknownOption(ProcessError)(cause); return Option.isSome(decoded) ? new ProcessStartError(decoded.value) : new ProcessStartError({ message: String(cause) }); }; /** * Owns one child-process attempt from spawn through close, startup error, or interruption. * * Rust performs one blocking `Command::output` call and keeps the trace directory alive * through `TempDir` ownership. Node exposes asynchronous close/error events instead, so * this adapter uses an Effect scope as the corresponding resource-ownership boundary. * https://github.com/informalsystems/quint-connect/blob/4f018f54fc7dd4cef341d10111427bab59d3b307/connect/src/trace/generator/mod.rs#L23-L33 */ export const runManagedProcess = (options) => Effect.scoped(Effect.gen(function* () { const { lifecycle, process } = yield* Effect.acquireRelease(Effect.try({ try: options.spawn, catch: processStartError }).pipe(Effect.map((process) => ({ lifecycle: options.processBoundary.makeLifecycle(() => process), process }))), ({ lifecycle }) => lifecycle.interrupt); const makeResult = yield* Effect.try({ try: () => options.captureResult(process), catch: processStartError }); return yield* Effect.async((resume) => { process.on("close", (code) => { lifecycle.complete(); resume(Effect.succeed(makeResult(code ?? 1))); }); process.on("error", (cause) => { const error = processStartError(cause); // Node-only decision: unlike Rust's blocking `Command::output`, an `error` // event is not proof that an already-created child has exited. ENOENT means // spawn never created one; otherwise the open scope retains ownership and its // finalizer terminates any possibly-live process tree. // Rust comparison: // https://github.com/informalsystems/quint-connect/blob/4f018f54fc7dd4cef341d10111427bab59d3b307/connect/src/trace/generator/mod.rs#L23-L30 // Node event contract: https://nodejs.org/api/child_process.html#event-error if (error.code === "ENOENT") { lifecycle.complete(); } resume(Effect.fail(error)); }); return Effect.void; }); })); //# sourceMappingURL=managed-process.js.map