fix(source-maps): preserve failure priority and cancellation provenance

This commit is contained in:
morluto committed 2026-10-20 17:18:20 +08:00
1 parent cc73587ec4
commit be20ac3740
6 files changed
+627 -59

No files matched your search

@@ -0,0 +1,278 @@
import { access } from "node:fs/promises";
import { expect, it, vi } from "vitest";
import {
AnalysisCancelledError,
AnalysisResourceConstraintError,
AnalysisTimeoutError,
} from "../../domain/analysisErrorCore.js";
import { PrivateRuntimeRoot } from "../../process/PrivateRuntimeRoot.js";
import {
ProviderProcessSupervisor,
spawnOwnedProviderProcess,
} from "../../process/ProviderProcess.js";
import { WEB_SOURCE_MAP_LIMITS } from "../../domain/webSourceLocation.js";
import {
runSourceMapCommand,
SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES,
} from "./SourceMapCommand.js";
const runChild = (
signal: AbortSignal | undefined,
script: string,
onRuntime?: (path: string) => void,
onLaunch?: () => void,
) =>
runSourceMapCommand(
"{}",
"/probe/source.map",
signal === undefined ? undefined : { signal },
{
prepareOwnership: async () => {},
createRuntime: async () => {
const runtime = await PrivateRuntimeRoot.create({
prefix: "rea-source-map-command-test-",
});
onRuntime?.(runtime.path);
return runtime;
},
launcher: async (options) => {
const child = await spawnOwnedProviderProcess({
...options,
arguments: ["-e", script],
});
onLaunch?.();
return child;
},
},
);
const exitsAfterHoldingOutput =
'const {spawn}=require("node:child_process"); spawn(process.execPath,["-e","setTimeout(()=>{},400)"],{stdio:["ignore","inherit","inherit"]}); process.stderr.write("physical exit 7"); process.exit(7)';
const exitsThenDescendantExceedsStderrBudget =
'const {spawn}=require("node:child_process"); spawn(process.execPath,["-e",`setTimeout(()=>{process.stderr.write("x".repeat(1024*1024+64)); setInterval(()=>{},1000)},25)`],{stdio:["ignore","inherit","inherit"]}); process.stderr.write("physical exit 7"); process.exit(7)';
it("preserves the exact object reason for an already-aborted caller", async () => {
const reason = { caller: "opaque source-map request" };
const controller = new AbortController();
controller.abort(reason);
const result = await runSourceMapCommand(
"{}",
"/probe/source.map",
{ signal: controller.signal },
{
prepareOwnership: async () => {},
launcher: async () => {
throw new Error("must not launch");
},
},
);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected cancellation");
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
expect(result.error.cause).toBe(reason);
});
it("preserves exact caller reason when cancellation precedes process exit", async () => {
const reason = { caller: "active source-map cancellation" };
const controller = new AbortController();
let runtimePath: string | undefined;
const result = await runChild(
controller.signal,
'process.stdout.write("ready\\n"); setInterval(()=>{},1000)',
(path) => {
runtimePath = path;
},
() => setTimeout(() => controller.abort(reason), 0),
);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected cancellation");
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
expect(result.error.cause).toBe(reason);
if (runtimePath === undefined) throw new Error("runtime was not acquired");
await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" });
});
it("preserves an observed nonzero exit before caller cancellation during output drain", async () => {
const reason = { caller: "later than owned exit" };
const controller = new AbortController();
let runtimePath: string | undefined;
let observedBeforeAbort:
| { readonly code: number | null; readonly signal: NodeJS.Signals | null }
| undefined;
const originalWait = ProviderProcessSupervisor.prototype.waitForOutputClose;
const waitSpy = vi
.spyOn(ProviderProcessSupervisor.prototype, "waitForOutputClose")
.mockImplementation(async function (
this: ProviderProcessSupervisor,
timeoutMs,
signal,
) {
const exit = this.observedExit();
if (exit?.code === 7 && !controller.signal.aborted) {
observedBeforeAbort = exit;
controller.abort(reason);
return false;
}
return originalWait.call(this, timeoutMs, signal);
});
let result;
try {
result = await runChild(
controller.signal,
exitsAfterHoldingOutput,
(path) => {
runtimePath = path;
},
);
} finally {
waitSpy.mockRestore();
}
expect(observedBeforeAbort).toEqual({ code: 7, signal: null });
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected physical failure");
expect(result.error._tag).toBe("ProviderAdapterError");
expect(result.error.diagnostics).toMatchObject({
exit_code: 7,
output_complete: false,
});
expect(result.error.capturedOutput?.truncated).toBe(true);
if (runtimePath === undefined) throw new Error("runtime was not acquired");
await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" });
});
it("classifies the same nonzero exit without cancellation", async () => {
const result = await runChild(undefined, exitsAfterHoldingOutput);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected physical failure");
expect(result.error).toMatchObject({
_tag: "ProviderAdapterError",
diagnostics: {
exit_code: 7,
stderr: "physical exit 7",
output_complete: true,
},
});
});
it("stops a held drain after late output exceeds budget but keeps the exit primary", async () => {
const startedAt = performance.now();
const result = await runChild(
undefined,
exitsThenDescendantExceedsStderrBudget,
);
const elapsedMs = performance.now() - startedAt;
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected physical failure");
expect(result.error._tag).toBe("ProviderAdapterError");
expect(result.error.diagnostics).toMatchObject({
exit_code: 7,
output_limit_reached: true,
output_complete: false,
});
expect(result.error.diagnostics?.stderr).toContain("physical exit 7");
expect(result.error.capturedOutput?.truncated).toBe(true);
expect(elapsedMs).toBeLessThan(5_000);
});
it("preserves an observed process-memory limit over the codec exit status", async () => {
const controller = new AbortController();
const reason = { caller: "after memory-limit exit" };
let observedBeforeAbort:
| { readonly code: number | null; readonly signal: NodeJS.Signals | null }
| undefined;
const originalWait = ProviderProcessSupervisor.prototype.waitForOutputClose;
const waitSpy = vi
.spyOn(ProviderProcessSupervisor.prototype, "waitForOutputClose")
.mockImplementation(async function (
this: ProviderProcessSupervisor,
timeoutMs,
signal,
) {
const exit = this.observedExit();
if (exit?.code === 3 && !controller.signal.aborted) {
observedBeforeAbort = exit;
controller.abort(reason);
return false;
}
return originalWait.call(this, timeoutMs, signal);
});
let result;
try {
result = await runSourceMapCommand(
"{}",
"/probe/source.map",
{ signal: controller.signal },
{
prepareOwnership: async () => {},
launcher: async (options) => {
const spawned = await spawnOwnedProviderProcess({
...options,
arguments: [
"-e",
'const {spawn}=require("node:child_process"); spawn(process.execPath,["-e","setTimeout(()=>{},400)"],{stdio:["ignore","inherit","inherit"]}); process.stderr.write("memory limit reached"); process.exit(3)',
],
});
let exited = false;
spawned.process.once("exit", () => {
exited = true;
});
Object.defineProperty(spawned.process, "memoryLimitReached", {
get: () => exited,
});
return spawned;
},
},
);
} finally {
waitSpy.mockRestore();
}
expect(observedBeforeAbort).toEqual({ code: 3, signal: null });
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected memory-limit failure");
expect(result.error).toBeInstanceOf(AnalysisResourceConstraintError);
expect(result.error).toMatchObject({
resource: "memory",
reportedLimits: {
codec_process_memory_limit_bytes: SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES,
},
capturedOutput: { stderr: "memory limit reached" },
});
expect(result.error.capturedOutput?.truncated).toBe(true);
});
it("keeps a timeout distinct from caller cancellation", async () => {
vi.useFakeTimers({
toFake: ["setTimeout", "clearTimeout", "performance"],
});
let runtimePath: string | undefined;
try {
const result = await runSourceMapCommand(
"{}",
"/probe/source.map",
undefined,
{
prepareOwnership: async () => {},
createRuntime: async () => {
const runtime = await PrivateRuntimeRoot.create({
prefix: "rea-source-map-timeout-test-",
});
runtimePath = runtime.path;
return runtime;
},
launcher: async () => {
await vi.advanceTimersByTimeAsync(
WEB_SOURCE_MAP_LIMITS.decodeTimeoutMs + 1,
);
throw new Error("launcher settled after deadline");
},
},
);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected timeout");
expect(result.error).toBeInstanceOf(AnalysisTimeoutError);
if (runtimePath === undefined) throw new Error("runtime was not acquired");
await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
vi.useRealTimers();
}
});
+235 -52
View File
@@ -25,6 +25,8 @@ import {
ProviderProcessSupervisor,
spawnOwnedProviderProcess,
type OwnedProviderProcessSpawnOptions,
type ProviderProcessExitObservation,
type ProviderProcessSnapshot,
type SpawnedOwnedProviderProcess,
} from "../../process/ProviderProcess.js";
import { prepareProcessOwnershipInspection } from "../../process/ProcessOwnershipObservation.js";
@@ -58,6 +60,25 @@ export interface SourceMapCleanupOwner {
): Promise<ProviderCleanupError | undefined>;
}
type CodecPhysicalObservation =
| {
readonly kind: "exit";
readonly exit: ProviderProcessExitObservation;
}
| {
readonly kind: "process-error";
readonly message: string;
readonly cause: Error;
}
| { readonly kind: "output-limit" };
type CodecOutcome =
| {
readonly kind: "physical";
readonly observation: CodecPhysicalObservation;
}
| { readonly kind: "cancelled"; readonly callerReason: unknown }
| { readonly kind: "timeout" };
export class SourceMapCleanupFailure extends ProviderCleanupError {
constructor(
readonly cleanupOwner: SourceMapCleanupOwner,
@@ -82,7 +103,9 @@ export const runSourceMapCommand = async (
} catch (cause: unknown) {
return err(
options?.signal?.aborted === true
? new AnalysisCancelledError(OPERATION)
? new AnalysisCancelledError(OPERATION, {
cause: options.signal.reason,
})
: new ProviderAdapterError(PROVIDER, OPERATION, {
cause,
diagnostics: {
@@ -105,12 +128,15 @@ export const runSourceMapCommand = async (
} catch (cause: unknown) {
result = err(command.failure(cause));
} finally {
command.dispose();
deadline.dispose();
}
const cleanup = await command.close(result.ok ? null : result.error.message);
if (cleanup !== undefined) return err(cleanup);
return result.ok && options?.signal?.aborted
? err(new AnalysisCancelledError(OPERATION))
? err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
)
: result;
};
@@ -118,13 +144,26 @@ class OwnedCodec implements SourceMapCleanupOwner {
readonly #runId = `rea-source-map-${randomUUID()}`;
#runtime: SourceMapCodecRuntime | undefined;
#supervisor: ProviderProcessSupervisor | undefined;
#oversized = false;
#processError: string | undefined;
#outcome: CodecOutcome | undefined;
#drainStop: CodecPhysicalObservation | undefined;
readonly #onInterruption: () => void;
constructor(
readonly path: string,
readonly dependencies: SourceMapDecoderDependencies,
readonly deadline: ProviderStartupDeadline,
) {}
) {
this.#onInterruption = () => this.#observeInterruption();
if (deadline.signal.aborted) this.#observeInterruption();
else
deadline.signal.addEventListener("abort", this.#onInterruption, {
once: true,
});
}
/** Stop recording event order after an outcome has been selected. */
dispose(): void {
this.deadline.signal.removeEventListener("abort", this.#onInterruption);
}
async start(request: string): Promise<void> {
this.check();
@@ -175,16 +214,33 @@ class OwnedCodec implements SourceMapCleanupOwner {
(() => cleanupOwnedProcessGroup(spawned.ownership)),
},
{
maxDiagnosticBytes: WEB_SOURCE_MAP_LIMITS.outputBytes + 1024 * 1024,
onDiagnostic: (event) => {
if (event.type === "error") this.#processError = event.message;
if (
event.type === "exit" &&
(event.code !== 0 || event.signal !== null)
)
this.#recordPhysicalFailure({
kind: "exit",
exit: { code: event.code, signal: event.signal },
});
if (event.type === "error") {
this.#recordPhysicalFailure({
kind: "process-error",
message: event.message,
cause: event.cause ?? new Error(event.message),
});
}
if (
event.type === "output" &&
event.totalBytes >
(event.stream === "stdout"
? WEB_SOURCE_MAP_LIMITS.outputBytes
: 1024 * 1024)
)
this.#oversized = true;
(event.truncated === true ||
event.totalBytes >
(event.stream === "stdout"
? WEB_SOURCE_MAP_LIMITS.outputBytes
: 1024 * 1024))
) {
this.#recordPhysicalFailure({ kind: "output-limit" });
}
},
},
);
@@ -194,61 +250,50 @@ class OwnedCodec implements SourceMapCleanupOwner {
const supervisor = this.#supervisor;
if (supervisor === undefined)
throw new Error("Codec process was not acquired");
while (!(await supervisor.waitForOutputClose(50))) this.check();
while (!(await supervisor.waitForOutputClose(50))) {
const outcome = this.#outcome;
if (
outcome?.kind !== "physical" ||
outcome.observation.kind !== "exit" ||
this.deadline.signal.aborted ||
this.#drainStop !== undefined
)
this.check();
}
this.check();
const snapshot = supervisor.snapshot();
// No partial codec result exists; a reached limit fails the whole trace.
if (snapshot.memoryLimitReached === true)
throw new AnalysisResourceConstraintError(
OPERATION,
"memory",
`Source-map codec reached its ${SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES === null ? "" : `${String(SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES)}-byte `}process memory limit for ${this.path}; no partial evidence was returned.`,
{
codec_process_memory_limit_bytes: SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES,
},
{
capturedOutput: {
stdout: "",
stderr: snapshot.stderr.text,
truncated: snapshot.diagnosticTruncated ?? false,
},
},
);
if (snapshot.exitCode !== 0 || snapshot.signal !== null)
throw new ProviderAdapterError(PROVIDER, OPERATION, {
diagnostics: {
map_path: this.path,
exit_code: snapshot.exitCode ?? null,
signal: snapshot.signal ?? null,
stderr: snapshot.stderr.text,
},
});
throw this.#memoryLimitFailure(snapshot);
return snapshot.stdout.text;
}
check(): void {
if (this.deadline.interruption === "cancelled")
throw new AnalysisCancelledError(OPERATION);
if (this.deadline.interruption === "timeout")
this.#observeInterruption();
const outcome = this.#outcome;
if (outcome?.kind === "physical")
throw this.#physicalFailure(outcome.observation);
if (outcome?.kind === "cancelled")
throw new AnalysisCancelledError(OPERATION, {
cause: outcome.callerReason,
});
if (outcome?.kind === "timeout")
throw new AnalysisTimeoutError(
OPERATION,
WEB_SOURCE_MAP_LIMITS.decodeTimeoutMs,
);
if (this.#oversized)
throw new AnalysisOutputError(
OPERATION,
"Codec output exceeds its complete-evidence or diagnostic budget.",
);
if (this.#processError !== undefined)
throw new ProviderAdapterError(PROVIDER, OPERATION, {
diagnostics: { map_path: this.path, error_message: this.#processError },
});
}
failure(cause: unknown): AnalysisError {
if (this.deadline.interruption === "cancelled")
return new AnalysisCancelledError(OPERATION);
if (this.deadline.interruption === "timeout")
this.#observeInterruption();
const outcome = this.#outcome;
if (outcome?.kind === "physical")
return this.#physicalFailure(outcome.observation);
if (outcome?.kind === "cancelled")
return new AnalysisCancelledError(OPERATION, {
cause: outcome.callerReason,
});
if (outcome?.kind === "timeout")
return new AnalysisTimeoutError(
OPERATION,
WEB_SOURCE_MAP_LIMITS.decodeTimeoutMs,
@@ -267,6 +312,144 @@ class OwnedCodec implements SourceMapCleanupOwner {
});
}
#observeInterruption(): void {
if (this.#outcome !== undefined) return;
if (!this.deadline.signal.aborted) return;
const interruption = this.deadline.interruption;
if (interruption === undefined) return;
this.#outcome =
interruption === "cancelled"
? { kind: "cancelled", callerReason: this.deadline.signal.reason }
: { kind: "timeout" };
}
#recordPhysicalFailure(observation: CodecPhysicalObservation): void {
this.#observeInterruption();
if (this.#outcome === undefined)
this.#outcome = { kind: "physical", observation };
else if (observation.kind !== "exit" && this.#drainStop === undefined)
this.#drainStop = observation;
}
#physicalFailure(observation: CodecPhysicalObservation): AnalysisError {
const snapshot = this.#supervisor?.snapshot();
if (observation.kind === "exit") {
if (snapshot?.memoryLimitReached === true)
return this.#memoryLimitFailure(snapshot);
return this.#exitFailure(observation.exit, snapshot);
}
if (observation.kind === "process-error")
return this.#processErrorFailure(observation, snapshot);
return this.#outputLimitFailure(snapshot);
}
#exitFailure(
exit: ProviderProcessExitObservation,
snapshot: ProviderProcessSnapshot | undefined,
): AnalysisError {
const outputComplete = this.#supervisor?.observedOutputClose() ?? false;
const capturedOutput = this.#capturedOutput(snapshot, exit, outputComplete);
return new ProviderAdapterError(PROVIDER, OPERATION, {
diagnostics: {
map_path: this.path,
exit_code: exit.code,
signal: exit.signal,
output_complete: outputComplete,
stderr: snapshot?.stderr.text ?? "",
...(this.#drainStop?.kind === "output-limit"
? { output_limit_reached: true }
: {}),
...(this.#drainStop?.kind === "process-error"
? { drain_error_message: this.#drainStop.message }
: {}),
},
...(capturedOutput === undefined ? {} : { capturedOutput }),
});
}
#processErrorFailure(
observation: Extract<CodecPhysicalObservation, { kind: "process-error" }>,
snapshot: ProviderProcessSnapshot | undefined,
): AnalysisError {
const outputComplete = this.#supervisor?.observedOutputClose() ?? false;
const capturedOutput = this.#capturedOutput(
snapshot,
undefined,
outputComplete,
);
return new ProviderAdapterError(PROVIDER, OPERATION, {
cause: observation.cause,
diagnostics: {
map_path: this.path,
error_message: observation.message,
output_complete: outputComplete,
},
...(capturedOutput === undefined ? {} : { capturedOutput }),
});
}
#outputLimitFailure(
snapshot: ProviderProcessSnapshot | undefined,
): AnalysisError {
const outputComplete = this.#supervisor?.observedOutputClose() ?? false;
const capturedOutput = this.#capturedOutput(
snapshot,
undefined,
outputComplete,
);
return new AnalysisOutputError(
OPERATION,
"Codec output exceeds its complete-evidence or diagnostic budget.",
{
capturedOutput: {
stdout: capturedOutput?.stdout ?? "",
stderr: capturedOutput?.stderr ?? "",
stdout_bytes: capturedOutput?.stdout_bytes ?? 0,
stderr_bytes: capturedOutput?.stderr_bytes ?? 0,
truncated: true,
},
},
);
}
#capturedOutput(
snapshot: ProviderProcessSnapshot | undefined,
exit: ProviderProcessExitObservation | undefined,
outputComplete: boolean,
) {
if (snapshot === undefined) return undefined;
return {
stdout: snapshot.stdout.text,
stderr: snapshot.stderr.text,
truncated: snapshot.diagnosticTruncated === true || !outputComplete,
stdout_bytes: snapshot.stdout.observedBytes,
stderr_bytes: snapshot.stderr.observedBytes,
...(exit === undefined
? {}
: { exit_code: exit.code, signal: exit.signal }),
};
}
#memoryLimitFailure(snapshot: ProviderProcessSnapshot): AnalysisError {
return new AnalysisResourceConstraintError(
OPERATION,
"memory",
`Source-map codec reached its ${SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES === null ? "" : `${String(SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES)}-byte `}process memory limit for ${this.path}; no partial evidence was returned.`,
{
codec_process_memory_limit_bytes: SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES,
},
{
capturedOutput: {
stdout: "",
stderr: snapshot.stderr.text,
truncated:
snapshot.diagnosticTruncated === true ||
!(this.#supervisor?.observedOutputClose() ?? false),
},
},
);
}
async close(
previousError: string | null,
): Promise<ProviderCleanupError | undefined> {
@@ -0,0 +1,93 @@
import { access } from "node:fs/promises";
import { expect, it } from "vitest";
import { SourceMapDecoder } from "./SourceMapDecoder.js";
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
import {
SourceMapCleanupFailure,
type SourceMapDecoderDependencies,
} from "./SourceMapCommand.js";
import { PrivateRuntimeRoot } from "../../process/PrivateRuntimeRoot.js";
import { spawnOwnedProviderProcess } from "../../process/ProviderProcess.js";
import {
webSourceLocationArgs,
webSourceLocationFixture,
} from "../../../tests/fixtures/webSourceLocation.js";
const fixture = webSourceLocationFixture();
const input = {
text: fixture.sourceMap.text,
url: fixture.sourceMap.url,
path: fixture.sourceMap.file.path,
position: webSourceLocationArgs.generated_position,
};
it("preserves the exact caller reason before source-map admission", async () => {
const reason = { caller: "pre-aborted source-map decode" };
const controller = new AbortController();
controller.abort(reason);
const response = await new SourceMapDecoder().trace(input, {
signal: controller.signal,
});
expect(response.ok).toBe(false);
if (response.ok) throw new Error("expected cancellation");
expect(response.error).toBeInstanceOf(AnalysisCancelledError);
expect(response.error.cause).toBe(reason);
});
it("retains failed cleanup ownership and retries it before another decode", async () => {
const root = await PrivateRuntimeRoot.create({
prefix: "rea-source-map-cleanup-retry-test-",
});
let cleanupAttempts = 0;
const dependencies: SourceMapDecoderDependencies = {
prepareOwnership: async () => {},
createRuntime: async () => ({
path: root.path,
close: async () => {
cleanupAttempts += 1;
if (cleanupAttempts === 1)
throw new Error("injected root cleanup failure");
await root.close();
},
}),
launcher: async (options) =>
spawnOwnedProviderProcess({
...options,
arguments: [
"-e",
'process.stderr.write("physical exit 7"); process.exit(7)',
],
}),
};
const decoder = new SourceMapDecoder(dependencies);
try {
const failed = await decoder.trace(input);
expect(failed.ok).toBe(false);
if (failed.ok)
throw new Error("expected physical failure with cleanup failure");
expect(failed.error).toBeInstanceOf(SourceMapCleanupFailure);
if (!(failed.error instanceof SourceMapCleanupFailure))
throw new Error("expected source-map cleanup ownership");
expect(failed.error.cleanupOwner).toBeDefined();
expect(failed.error.diagnostics).toMatchObject({
cleanup_failures: ["injected root cleanup failure"],
});
expect(failed.error.diagnostics).toMatchObject({
previous_error: expect.stringContaining(
"source-map-decoder adapter failed",
),
});
await expect(access(root.path)).resolves.toBeUndefined();
const invalidInput = { ...input, text: null } as unknown as typeof input;
const retried = await decoder.trace(invalidInput);
expect(retried.ok).toBe(false);
if (retried.ok)
throw new Error("expected invalid input after cleanup retry");
expect(retried.error._tag).toBe("AnalysisInputError");
expect(cleanupAttempts).toBe(2);
await expect(access(root.path)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
await decoder.close();
}
});
@@ -79,7 +79,9 @@ export class SourceMapDecoder implements WebSourceMapPort {
const cleanupFailure = await this.#retryCleanup();
if (cleanupFailure !== undefined) return err(cleanupFailure);
if (options?.signal?.aborted)
return err(new AnalysisCancelledError(OPERATION));
return err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
);
const request = sourceMapCodecInputSchema.safeParse({
text: input.text,
url: input.url,
@@ -106,7 +108,9 @@ export class SourceMapDecoder implements WebSourceMapPort {
encodedBytes += bytes;
}
if (options?.signal?.aborted)
return err(new AnalysisCancelledError(OPERATION));
return err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
);
const serialized = encoded.join("");
const response = await runSourceMapCommand(
serialized,
@@ -139,7 +143,9 @@ export class SourceMapDecoder implements WebSourceMapPort {
),
);
if (options?.signal?.aborted)
return err(new AnalysisCancelledError(OPERATION));
return err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
);
if (reply.data.state === "failure")
return codecFailure(input.path, reply.data.reason, reply.data.message);
const runtimeFailure = codecRuntimeFailure(reply.data.report.runtime);