https://github.com/mnrendra/stack-trace
A lightweight stack trace utility to retrieve CallSite objects from a specific caller.
https://github.com/mnrendra/stack-trace
callsite callsites stack-trace stack-trace-v8 stack-traces
Last synced: 26 days ago
JSON representation
A lightweight stack trace utility to retrieve CallSite objects from a specific caller.
- Host: GitHub
- URL: https://github.com/mnrendra/stack-trace
- Owner: mnrendra
- License: mit
- Created: 2023-07-12T06:05:57.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2025-04-29T15:00:50.000Z (10 months ago)
- Last Synced: 2025-04-29T15:58:02.614Z (10 months ago)
- Topics: callsite, callsites, stack-trace, stack-trace-v8, stack-traces
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/@mnrendra/stack-trace
- Size: 557 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# @mnrendra/stack-trace
[](https://www.npmjs.com/package/@mnrendra/stack-trace?activeTab=versions)
[](https://npm-stat.com/charts.html?package=@mnrendra/stack-trace)
[](https://packagephobia.com/result?p=%40mnrendra%2Fstack-trace)
[](https://app.codecov.io/gh/mnrendra/stack-trace)
[](https://securityscorecards.dev/viewer/?uri=github.com/mnrendra/stack-trace)
[](https://github.com/mnrendra/stack-trace/actions/workflows/release.yml)
[](https://github.com/mnrendra/stack-trace/commits/main)
[](https://github.com/mnrendra/stack-trace/blob/main/LICENSE)
A lightweight [stack trace](https://v8.dev/docs/stack-trace-api) utility to retrieve `CallSite` objects from a specific caller.
*Useful for debugging, logging, or building tools that need to trace call origins at runtime.*
## Features
- ✅ Supports both **ES Modules** and **CommonJS** from a single source - see [package](https://www.npmjs.com/package/@mnrendra/stack-trace?activeTab=code)
- ✅ Minified and cleansed of unnecessary dependencies, files, and attributes - see [contents](https://www.npmjs.com/package/@mnrendra/stack-trace?activeTab=code)
- ✅ Tiny package - see [size](https://bundlephobia.com/package/@mnrendra/stack-trace)
- ✅ Well tested - see [coverage](https://app.codecov.io/gh/mnrendra/stack-trace)
- ✅ Security checked - see [scorecard](https://securityscorecards.dev/viewer/?uri=github.com/mnrendra/stack-trace)
- ✅ Verified all commits - see [signatures](https://github.com/mnrendra/stack-trace/commits/main)
- ✅ Semantic versioning - see [commits](https://github.com/mnrendra/stack-trace/commits/main)
- ✅ Actively maintained - [pull requests](https://github.com/mnrendra/stack-trace/pulls), [issues](https://github.com/mnrendra/stack-trace/issues), [discussions](https://github.com/mnrendra/stack-trace/discussions), and [contributions](https://github.com/mnrendra/stack-trace/blob/HEAD/CONTRIBUTING.md) are welcome!
## Install
```bash
npm i @mnrendra/stack-trace
```
## API Reference
### `stackTrace`
Captures [v8 stack trace](https://v8.dev/docs/stack-trace-api) from a specific caller.
#### Type
```typescript
(callee?: ((...args: any) => any) | null, options?: Options) => NodeJS.CallSite[]
```
#### Parameters
| Name | Type | Description |
|-----------|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `callee` | `((...args: any) => any) \| null` | Optional callee function to specify the caller. If `undefined` or `null`, tracing starts from the current caller. |
| `options` | `Options` | Optional options to affect the captured frames. By default, the `limit` option is set to `Infinity` to capture all frames. To capture only a specific number of frames, set the `limit` option to a positive number. |
#### Return
```typescript
NodeJS.CallSite[]
```
Array of `CallSite` objects representing the captured stack trace frames.
#### Options
| Name | Type | Default | Description |
|---------|----------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `limit` | `number` | `Infinity` | Specifies the number of stack frames to be collected by a stack trace. The default value is `Infinity`, but may be set to any valid JavaScript number. Changes will affect any stack trace captured after the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. |
### `getCallerSite`
Gets the caller's `CallSite` object captured from [`stackTrace`](#stacktrace).
#### Type
```typescript
(callee?: ((...args: any) => any) | null) => NodeJS.CallSite
```
#### Parameters
| Name | Type | Description |
|-----------|-----------------------------------|-------------------------------------------------------------------------------------------------------------------|
| `callee` | `((...args: any) => any) \| null` | Optional callee function to specify the caller. If `undefined` or `null`, tracing starts from the current caller. |
#### Return
```typescript
NodeJS.CallSite
```
First `CallSite` object captured in the stack trace.
### `extractFilePath`
Extracts the file name from a `CallSite` object and converts it to a file path if the value is a file URL.
*This utility ensures that the returned value is an absolute path.*
#### Type
```typescript
(callSite: NodeJS.CallSite) => string
```
#### Parameters
| Name | Type | Description |
|-------------|-------------------|--------------------------------------------------------------|
| `callSite` | `NodeJS.CallSite` | `CallSite` object captured from [`stackTrace`](#stacktrace). |
#### Return
```typescript
string
```
Absolute path of the file name extracted from a `CallSite` object.
#### Throws
If the extracted file name is not a string or not absolute.
### `getCallerFile`
Gets the caller's file extracted from the result of [`getCallerSite`](#getcallersite) and ensures it returns an absolute path using [`extractFilePath`](#extractfilepath).
#### Type
```typescript
(callee?: ((...args: any) => any) | null) => string
```
#### Parameters
| Name | Type | Description |
|-----------|-----------------------------------|-------------------------------------------------------------------------------------------------------------------|
| `callee` | `((...args: any) => any) \| null` | Optional callee function to specify the caller. If `undefined` or `null`, tracing starts from the current caller. |
#### Return
```typescript
string
```
Absolute path of the caller's file.
#### Throws
If the extracted file name is not a string or not absolute.
### `getCallerDir`
Gets the caller's directory extracted from the result of [`getCallerFile`](#getcallerfile).
#### Type
```typescript
(callee?: ((...args: any) => any) | null) => string
```
#### Parameters
| Name | Type | Description |
|-----------|-----------------------------------|-------------------------------------------------------------------------------------------------------------------|
| `callee` | `((...args: any) => any) \| null` | Optional callee function to specify the caller. If `undefined` or `null`, tracing starts from the current caller. |
#### Return
```typescript
string
```
Absolute path of the caller's directory.
#### Throws
If the extracted file name is not a string or not absolute.
## Usage
### ES Modules
`/foo/callee.mjs`
```javascript
import { dirname } from 'node:path'
import {
stackTrace,
getCallerSite,
extractFilePath,
getCallerFile,
getCallerDir
} from '@mnrendra/stack-trace'
const callee = () => {
// `stackTrace`:
const [callSite1] = stackTrace()
const [callSite2] = stackTrace(callee, { limit: 1 }) // Pass the `callee` function as the callee.
console.log(callSite1.getFileName()) // Output: file:///foo/callee.mjs
console.log(callSite2.getFileName()) // Output: file:///foo/caller.mjs
console.log(callSite1.getFunctionName()) // Output: callee
console.log(callSite2.getFunctionName()) // Output: caller
// `getCallerSite`:
const callerSite1 = getCallerSite()
const callerSite2 = getCallerSite(callee) // Pass the `callee` function as the callee.
console.log(callerSite1.getFileName() === callSite1.getFileName()) // Output: true
console.log(callerSite2.getFileName() === callSite2.getFileName()) // Output: true
console.log(callerSite1.getFileName()) // Output: file:///foo/callee.mjs
console.log(callerSite2.getFileName()) // Output: file:///foo/caller.mjs
console.log(callerSite1.getFunctionName() === callSite1.getFunctionName()) // Output: true
console.log(callerSite2.getFunctionName() === callSite2.getFunctionName()) // Output: true
console.log(callerSite1.getFunctionName()) // Output: callee
console.log(callerSite2.getFunctionName()) // Output: caller
// `extractFilePath`:
const filePath1 = extractFilePath(callerSite1)
const filePath2 = extractFilePath(callerSite2)
console.log(filePath1) // Output: /foo/callee.mjs
console.log(filePath2) // Output: /foo/caller.mjs
// `getCallerFile`:
const callerFile1 = getCallerFile()
const callerFile2 = getCallerFile(callee) // Pass the `callee` function as the callee.
console.log(callerFile1 === filePath1) // Output: true
console.log(callerFile2 === filePath2) // Output: true
console.log(callerFile1) // Output: /foo/callee.mjs
console.log(callerFile2) // Output: /foo/caller.mjs
// `getCallerDir`:
const callerDir1 = getCallerDir()
const callerDir2 = getCallerDir(callee) // Pass the `callee` function as the callee.
console.log(callerDir1 === dirname(filePath1)) // Output: true
console.log(callerDir2 === dirname(filePath2)) // Output: true
console.log(callerDir1) // Output: /foo
console.log(callerDir2) // Output: /foo
}
export default callee
```
`/foo/caller.mjs`
```javascript
import callee from './callee.mjs'
const caller = () => callee()
caller()
```
### CommonJS
`/foo/callee.cjs`
```javascript
const { dirname } = require('node:path')
const {
stackTrace,
getCallerSite,
extractFilePath,
getCallerFile,
getCallerDir
} = require('@mnrendra/stack-trace')
const callee = () => {
// `stackTrace`:
const [callSite1] = stackTrace()
const [callSite2] = stackTrace(callee, { limit: 1 }) // Pass the `callee` function as the callee.
console.log(callSite1.getFileName()) // Output: /foo/callee.cjs
console.log(callSite2.getFileName()) // Output: /foo/caller.cjs
console.log(callSite1.getFunctionName()) // Output: callee
console.log(callSite2.getFunctionName()) // Output: caller
// `getCallerSite`:
const callerSite1 = getCallerSite()
const callerSite2 = getCallerSite(callee) // Pass the `callee` function as the callee.
console.log(callerSite1.getFileName() === callSite1.getFileName()) // Output: true
console.log(callerSite2.getFileName() === callSite2.getFileName()) // Output: true
console.log(callerSite1.getFileName()) // Output: /foo/callee.cjs
console.log(callerSite2.getFileName()) // Output: /foo/caller.cjs
console.log(callerSite1.getFunctionName() === callSite1.getFunctionName()) // Output: true
console.log(callerSite2.getFunctionName() === callSite2.getFunctionName()) // Output: true
console.log(callerSite1.getFunctionName()) // Output: callee
console.log(callerSite2.getFunctionName()) // Output: caller
// `extractFilePath`:
const filePath1 = extractFilePath(callerSite1)
const filePath2 = extractFilePath(callerSite2)
console.log(filePath1) // Output: /foo/callee.cjs
console.log(filePath2) // Output: /foo/caller.cjs
// `getCallerFile`:
const callerFile1 = getCallerFile()
const callerFile2 = getCallerFile(callee) // Pass the `callee` function as the callee.
console.log(callerFile1 === filePath1) // Output: true
console.log(callerFile2 === filePath2) // Output: true
console.log(callerFile1) // Output: /foo/callee.cjs
console.log(callerFile2) // Output: /foo/caller.cjs
// `getCallerDir`:
const callerDir1 = getCallerDir()
const callerDir2 = getCallerDir(callee) // Pass the `callee` function as the callee.
console.log(callerDir1 === dirname(filePath1)) // Output: true
console.log(callerDir2 === dirname(filePath2)) // Output: true
console.log(callerDir1) // Output: /foo
console.log(callerDir2) // Output: /foo
}
module.exports = callee
```
`/foo/caller.cjs`
```javascript
const callee = require('./callee.cjs')
const caller = () => callee()
caller()
```
> **Note**:
>
> - In ES Modules, `getFileName` returns a **file URL** (e.g., `file:///foo`), instead of a **file path** (`/foo`).
> *To convert it to a file path, use either `url.fileURLToPath` or the `extractFilePath` utility.*
>
> - By default `stackTrace` will capture all caller's frames.
> *To capture only a specific number of frames, set the `limit` option to a positive number.*
### Examples
1. **Call from a development project**
`/foo/project-name/src/index.mjs`:
```javascript
import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import {
stackTrace,
getCallerSite,
extractFilePath,
getCallerFile,
getCallerDir
} from '@mnrendra/stack-trace'
// `stackTrace`:
const caller1 = () => stackTrace()
const [callSite] = caller1()
const fileName = callSite.getFileName()
console.log(fileName) // Output: file:///foo/project-name/src/index.mjs
console.log(fileURLToPath(fileName)) // Output: /foo/project-name/src/index.mjs
// `getCallerSite`:
const caller2 = () => getCallerSite()
const callerSite = caller2()
const callerFileName = callerSite.getFileName()
console.log(callerFileName === fileName) // Output: true
console.log(callerFileName) // Output: file:///foo/project-name/src/index.mjs
console.log(fileURLToPath(callerFileName)) // Output: /foo/project-name/src/index.mjs
// `extractFilePath`:
const filePath = extractFilePath(callerSite)
console.log(filePath === fileURLToPath(callerFileName)) // Output: true
console.log(filePath) // Output: /foo/project-name/src/index.mjs
// `getCallerFile`:
const caller3 = () => getCallerFile()
const callerFile = caller3()
console.log(callerFile === filePath) // Output: true
console.log(callerFile) // Output: /foo/project-name/src/index.mjs
// `getCallerDir`:
const caller4 = () => getCallerDir()
const callerDir = caller4()
console.log(callerDir === dirname(filePath)) // Output: true
console.log(callerDir) // Output: /foo/project-name/src
```
2. **Call from a production package**
`/foo/consumer/node_modules/module-name/dist/index.cjs`:
```javascript
"use strict";
const { dirname } = require("node:path");
const {
stackTrace,
getCallerSite,
extractFilePath,
getCallerFile,
getCallerDir
} = require("@mnrendra/stack-trace");
// `stackTrace`:
const caller1 = () => stackTrace();
const [callSite] = caller1();
const fileName = callSite.getFileName();
console.log(fileName); // Output: /foo/consumer/node_modules/module-name/dist/index.cjs
// `getCallerSite`:
const caller2 = () => getCallerSite();
const callerSite = caller2();
const callerFileName = callerSite.getFileName();
console.log(callerFileName === fileName); // Output: true
console.log(callerFileName); // Output: /foo/consumer/node_modules/module-name/dist/index.cjs
// `extractFilePath`:
const filePath = extractFilePath(callerSite);
console.log(filePath === callerFileName); // Output: true
console.log(filePath); // Output: /foo/consumer/node_modules/module-name/dist/index.cjs
// `getCallerFile`:
const caller3 = () => getCallerFile()
const callerFile = caller3()
console.log(callerFile === filePath) // Output: true
console.log(callerFile) // Output: /foo/consumer/node_modules/module-name/dist/index.cjs
// `getCallerDir`:
const caller4 = () => getCallerDir();
const callerDir = caller4();
console.log(callerDir === dirname(filePath)); // Output: true
console.log(callerDir); // Output: /foo/consumer/node_modules/module-name/dist
```
## Types
### `Options`
[`stackTrace`](#stacktrace)'s [options](#options) interface.
```typescript
import {
type Options,
stackTrace
} from '@mnrendra/stack-trace'
const options: Options = {
limit: 1
}
const caller = (): NodeJS.CallSite[] => stackTrace(caller, options)
const callSites = caller()
console.log(callSites.length) // Output: 1
```
## Security
We take security seriously in this project. If you discover a **vulnerability**, we strongly encourage you to report it in a responsible manner.
Please open a [Security Advisory](https://github.com/mnrendra/stack-trace/security/advisories/new) to report any vulnerabilities.
For more information, please refer to our [Security Policy](https://github.com/mnrendra/stack-trace/blob/HEAD/SECURITY.md).
## Contributing
We appreciate your help in making this project better. Please follow the [guidelines](https://github.com/mnrendra/stack-trace/blob/HEAD/CONTRIBUTING.md) to ensure that your contributions are smoothly integrated.
## License
[MIT](https://github.com/mnrendra/stack-trace/blob/HEAD/LICENSE)
## Author
[@mnrendra](https://github.com/mnrendra)