🌳
pt0/sharedF/throwErrorF/sharedErrF.mts
1let _monorepoPrefix: string
2const getMonorepoPrefix = () => _monorepoPrefix ||= import.meta.url.replace(/pt0\/sharedF\/throwErrorF\/sharedErrF\.mts$/, '')
4export type DebugH = Record<string, unknown>
6export class PtErr extends Error {
7 uniqDebugH: DebugH | undefined
8 dumpDebugH: DebugH | undefined
9 throwLocation: string | undefined
11 constructor(message: string, uniqDebugH?: DebugH, dumpDebugH?: DebugH) {
12 super(message)
13 // non-enumerable to avoid duplicate printing by Node's error inspector
14 Object.defineProperty(this, 'uniqDebugH', { value: uniqDebugH, enumerable: false, writable: true })
15 Object.defineProperty(this, 'dumpDebugH', { value: { ...dumpDebugH }, enumerable: false, writable: true })
16 void this.stack
17 }
20const origPrepareStackTrace = Error.prepareStackTrace
21Error.prepareStackTrace = (err, callSites) => {
22 if (!(err instanceof PtErr)) {
23 return origPrepareStackTrace ? origPrepareStackTrace(err, callSites) : `${err.name}: ${err.message}\n` + callSites.map(s => ` at ${s}`).join('\n')
24 }
25 const monorepoPrefix = getMonorepoPrefix()
26 const isInternalFrame = (shortPath: string) => shortPath.includes('/throwErrorF/')
27 const callerSite = callSites.find(site => {
28 const fileName = site.getFileName() || ''
29 if (!fileName.startsWith(monorepoPrefix) || fileName.includes('[eval')) return false
30 return !isInternalFrame(fileName.replace(monorepoPrefix, ''))
31 })
32 if (callerSite) {
33 const shortPath = (callerSite.getFileName() || '').replace(monorepoPrefix, '')
34 const fnName = callerSite.getFunctionName() || '<anonymous>'
35 Object.defineProperty(err, 'throwLocation', { value: `${shortPath}:${fnName}`, enumerable: false, writable: true, configurable: true })
36 }
37 const monorepoFrames = callSites
38 .map(site => {
39 const fileName = site.getFileName() || ''
40 if (!fileName.startsWith(monorepoPrefix) || fileName.includes('[eval')) return null
41 const shortPath = fileName.replace(monorepoPrefix, '')
42 const fnName = site.getFunctionName() || '<anonymous>'
43 return ` at ${fnName} (${shortPath}:${site.getLineNumber()})`
44 })
45 .filter(Boolean) as string[]
46 const maxFrames = (err.uniqDebugH?._maxFrames as number | undefined) ?? Infinity
47 const shownFrames = monorepoFrames.slice(0, maxFrames)
48 const parts = [`PtErr: ${err.message}`, ...shownFrames]
49 const { _maxFrames, ...debugWithoutMaxFrames } = err.uniqDebugH || {}
50 if (Object.keys(debugWithoutMaxFrames).length > 0) parts.push(` uniqDebugH: ${JSON.stringify(debugWithoutMaxFrames)}`)
51 return parts.join('\n')
54export const throPtErr = (...props: [string, DebugH?, DebugH?]): never => {
55 throw new PtErr(...props)