diff --git a/examples/dev/metadata.eg.ts b/examples/dev/metadata.eg.ts index d50a7aab0..b26bb2b45 100644 --- a/examples/dev/metadata.eg.ts +++ b/examples/dev/metadata.eg.ts @@ -8,7 +8,6 @@ * even plain-text descriptions. This metadata serves as the basis off of which we * generate chain-specific APIs. Unless you are building an advanced Capi-based library, * chances are that you don't need to work with the metadata directly. - * @test_skip */ import { polkadotDev } from "@capi/polkadot-dev" diff --git a/rune/Rune.ts b/rune/Rune.ts index e6bad2516..0b000c427 100644 --- a/rune/Rune.ts +++ b/rune/Rune.ts @@ -1,7 +1,6 @@ import { deferred } from "../deps/std/async.ts" import { getOrInit } from "../util/state.ts" import { EventSource, Receipt, Timeline } from "./Timeline.ts" -import { Trace } from "./Trace.ts" // Hack to work around circularity issues // @deno-types="./_empty.d.ts" @@ -188,13 +187,11 @@ export class Rune { return Rune.new(RunPlaceholder) } - into>( + into( ctor: new(_prime: (batch: Batch) => Run>, ...args: A) => C, ...args: A ): C { - const rune = new ctor(this._prime, ...args) - rune._trace = this._trace - return rune + return new ctor(this._prime, ...args) } as(this: R, _ctor: new(_prime: (batch: Batch) => Run, ...args: any) => R): R { @@ -244,7 +241,16 @@ export abstract class Run { this._currentReceipt.novel = true } this._currentTime = time - this._currentPromise = this.trace.runAsync(() => this._evaluate(time, this._currentReceipt)) + this._currentPromise = (async () => { + try { + return await this._evaluate(time, this._currentReceipt) + } catch (e) { + if (e instanceof Error) { + e.message = `${e.message.trimEnd()}\n\n${this.trace.from}\n` + } + throw e + } + })() } const _receipt = this._currentReceipt try { @@ -426,3 +432,12 @@ export namespace RunicArgs { : Object.fromEntries(Object.entries(args).map(([k, v]) => [k, Rune.resolve(v)])) } } + +export class Trace extends Error { + declare from: string + constructor(name: string) { + super() + Object.defineProperty(this, "name", { value: name }) + Object.defineProperty(this, "from", { value: ("from " + this.stack).replace(/^/gm, " ") }) + } +} diff --git a/rune/Trace.test.ts b/rune/Trace.test.ts deleted file mode 100644 index a6be7db03..000000000 --- a/rune/Trace.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { assertEquals } from "../deps/std/testing/asserts.ts" -import { Trace } from "./Trace.ts" - -function makeWrapped(name: string, fn: (...args: A) => T) { - const trace = new Trace(name) - return (...args: A) => { - return trace.run(() => fn(...args)) - } -} - -function fooInner() { - throw new Error("wat") -} - -function makeFoo() { - return makeWrapped("foo", () => { - fooInner() - }) -} - -Deno.test("trace", () => { - const foo = makeFoo() - - try { - foo() - throw new Error("expected foo to throw") - } catch (e) { - assertEquals( - (e as Error).stack?.split(import.meta.url).join(".../Trace.test.ts").replace( - /\n^.*ext:cli.*$/gm, - "", - ), - ` -Error: wat - at fooInner (.../Trace.test.ts:12:9) - at .../Trace.test.ts:17:5 - at .../Trace.test.ts:7:28 - from foo - at makeWrapped (.../Trace.test.ts:5:17) - at makeFoo (.../Trace.test.ts:16:10) - at .../Trace.test.ts:22:15 - `.trim(), - ) - } -}) diff --git a/rune/Trace.ts b/rune/Trace.ts deleted file mode 100644 index 8786ecd7e..000000000 --- a/rune/Trace.ts +++ /dev/null @@ -1,184 +0,0 @@ -// @ts-ignore V8 special property -Error.stackTraceLimit = Infinity - -const traceLookup = new Map>() -const cleanUpLookup = new FinalizationRegistry((key: string) => { - traceLookup.delete(key) -}) - -export class Trace extends Error { - declare from: string - declare id: string - - constructor(name: string) { - super() - Object.defineProperty(this, "id", { value: marker + crypto.randomUUID() }) - traceLookup.set(this.id, new WeakRef(this)) - cleanUpLookup.register(this, this.id) - Object.defineProperty(this, "name", { value: name }) - Object.defineProperty(this, "from", { value: ("from " + this.stack).replace(/^/gm, " ") }) - } - - static _current?: Trace - - run(fn: () => T): T { - const prev = Trace._current - Trace._current = this - try { - return { - [this.id]() { - return fn() - }, - }[this.id]!() - } finally { - Trace._current = prev - } - } - - async runAsync(fn: () => Promise): Promise { - const prev = Trace._current - Trace._current = this - try { - return await { - async [this.id]() { - return await fn() - }, - }[this.id]!() - } finally { - Trace._current = prev - } - } -} - -const marker = "__capi_rune_trace_marker-" - -// @ts-ignore V8 special property -const original = Error.prepareStackTrace ?? prepareStackTrace - -// @ts-ignore V8 special property -Error.prepareStackTrace = (err, sites) => { - for (let i = 0; i < sites.length; i++) { - const fnName = sites[i]!.getFunctionName() - if (fnName?.startsWith(marker)) { - const trace = traceLookup.get(fnName)?.deref() - if (trace) { - return original(err, sites.slice(0, i)) + "\n" + trace.from - } - } - } - return original(err, sites) -} - -// Force deno to use the custom prepareStackTrace when an error isn't handled -if ("Deno" in globalThis) { - globalThis.addEventListener("error", (e) => { - handler(e.error) - }) - globalThis.addEventListener("unhandledrejection", (e) => { - handler(e.reason) - }) - // deno-lint-ignore no-inner-declarations - function handler(e: any) { - if (e instanceof Error) { - throw { - [Symbol.for("Deno.customInspect")](inspect: any, options: any) { - return inspect(e, options) - }, - } - } - } -} - -// Adapted from https://github.com/denoland/deno/blob/53487786/core/02_error.js - -function prepareStackTrace(error: Error, sites: any[]) { - const formattedCallSites = sites - .map(formatCallSite) - const message = error.message !== undefined ? error.message : "" - const name = error.name !== undefined ? error.name : "Error" - let messageLine - if (name != "" && message != "") { - messageLine = `${name}: ${message}` - } else if ((name || message) != "") { - messageLine = name || message - } else { - messageLine = "" - } - return messageLine + formattedCallSites.map((s) => `\n at ${s}`).join("") -} - -function formatLocation(site: any) { - if (site.isNative()) { - return "native" - } - let result = "" - if (site.fileName()) { - result += site.fileName() - } else { - if (site.isEval()) { - if (site.evalOrigin() == null) { - throw new Error("assert evalOrigin") - } - result += `${site.evalOrigin()}, ` - } - result += "" - } - if (site.lineNumber() != null) { - result += `:${site.lineNumber()}` - if (site.columnNumber() != null) { - result += `:${site.columnNumber()}` - } - } - return result -} - -function formatCallSite(site: any) { - let result = "" - if (site.isAsync()) { - result += "async " - } - if (site.isPromiseAll()) { - result += `Promise.all (index ${site.promiseIndex()})` - return result - } - const isMethodCall = !(site.isToplevel() || site.isConstructor()) - if (isMethodCall) { - if (site.functionName()) { - if (site.typeName()) { - if (!site.functionName.startsWith(site.typeName())) { - result += `${site.typeName()}.` - } - } - result += site.functionName() - if (site.methodName()) { - if (!site.functionName().endsWith(site.methodName())) { - result += ` [as ${site.methodName()}]` - } - } - } else { - if (site.typeName()) { - result += `${site.typeName()}.` - } - if (site.methodName()) { - result += site.methodName() - } else { - result += "" - } - } - } else if (site.isConstructor()) { - result += "new " - if (site.functionName()) { - result += site.functionName() - } else { - result += "" - } - } else if (site.functionName()) { - result += site.functionName() - } else { - result += formatLocation(site) - return result - } - - result += ` (${formatLocation(site)})` - return result -}