Even more better error messages

This commit is contained in:
Konstantin Wohlwend 2025-07-30 13:16:14 -07:00
parent b72353956b
commit e24d47b80f
4 changed files with 35 additions and 15 deletions

View File

@ -1,4 +1,5 @@
import { traceSpan } from '@/utils/telemetry';
import { StackAssertionError, captureError, errorToNiceString } from '@stackframe/stack-shared/dist/utils/errors';
import { FreestyleSandboxes } from 'freestyle-sandboxes';
export class TracedFreestyleSandboxes {

View File

@ -6,7 +6,7 @@ import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dis
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { globalVar } from "@stackframe/stack-shared/dist/utils/globals";
import { deepPlainEquals, filterUndefined, typedFromEntries, typedKeys } from "@stackframe/stack-shared/dist/utils/objects";
import { ignoreUnhandledRejection } from "@stackframe/stack-shared/dist/utils/promises";
import { concatStacktracesIfRejected, ignoreUnhandledRejection } from "@stackframe/stack-shared/dist/utils/promises";
import { Result } from "@stackframe/stack-shared/dist/utils/results";
import { isPromise } from "util/types";
import { runMigrationNeeded } from "./auto-migrations";
@ -305,8 +305,10 @@ async function rawQueryArray<Q extends RawQuery<any>[]>(tx: PrismaClientTransact
const postProcessed = combinedQuery.postProcess(rawResult as any);
// If the postProcess is async, postProcessed is a Promise. If that Promise is rejected, it will cause an unhandled promise rejection.
// We don't want that, because Vercel crashes on unhandled promise rejections.
// We also want to concat the current stack trace to the error, so we can see where the rawQuery function was called
if (isPromise(postProcessed)) {
ignoreUnhandledRejection(postProcessed);
concatStacktracesIfRejected(postProcessed);
}
return postProcessed;

View File

@ -28,6 +28,8 @@ function removeStacktraceNameLine(stack: string): string {
/**
* Concatenates the (original) stacktraces of the given errors onto the first.
*
* Note: Very often, the concatStacktracesIfRejected function in promises.tsx is an easier way to use this function.
*
* Useful when you invoke an async function to receive a promise without awaiting it immediately. Browsers are smart
* enough to keep track of the call stack in async function calls when you invoke `.then` within the same async tick,
* but if you don't, the stacktrace will be lost.
@ -36,7 +38,7 @@ function removeStacktraceNameLine(stack: string): string {
*
* ```tsx
* async function log() {
* await wait(0); // simulate an put the task on the event loop
* await wait(0); // put the task on the event loop
* console.log(new Error().stack);
* }
*

View File

@ -1,5 +1,5 @@
import { KnownError } from "..";
import { StackAssertionError, captureError, concatStacktraces } from "./errors";
import { StackAssertionError, captureError, concatStacktraces, errorToNiceString } from "./errors";
import { DependenciesMap } from "./maps";
import { Result } from "./results";
import { generateUuid } from "./uuids";
@ -243,6 +243,20 @@ import.meta.vitest?.test("ignoreUnhandledRejection", async ({ expect }) => {
await expect(rejectPromise).rejects.toBe(error);
});
/**
* See concatStacktraces for more information.
*/
export function concatStacktracesIfRejected<T>(promise: Promise<T>): void {
const currentError = new Error();
promise.catch(error => {
if (error instanceof Error) {
concatStacktraces(error, currentError);
} else {
// we can only concatenate errors, so we'll just ignore the non-error
}
});
}
export async function wait(ms: number) {
if (!Number.isFinite(ms) || ms < 0) {
throw new StackAssertionError(`wait() requires a non-negative integer number of milliseconds to wait. (found: ${ms}ms)`);
@ -336,18 +350,19 @@ export function runAsynchronously(
if (typeof promiseOrFunc === "function") {
promiseOrFunc = promiseOrFunc();
}
const duringError = new Error();
promiseOrFunc?.catch(error => {
options.onError?.(error);
const newError = new StackAssertionError(
"Uncaught error in asynchronous function: " + errorToNiceString(error),
{ cause: error },
);
concatStacktraces(newError, duringError);
if (!options.noErrorLogging) {
captureError("runAsynchronously", newError);
}
});
if (promiseOrFunc) {
concatStacktracesIfRejected(promiseOrFunc);
promiseOrFunc.catch(error => {
options.onError?.(error);
const newError = new StackAssertionError(
"Uncaught error in asynchronous function: " + errorToNiceString(error),
{ cause: error },
);
if (!options.noErrorLogging) {
captureError("runAsynchronously", newError);
}
});
}
}
import.meta.vitest?.test("runAsynchronously", ({ expect }) => {
// Simple test to verify the function exists and can be called