@stepper-io/dependency-parser
v1.0.0
Published
TypeScript AST-based dependency parser for Stepper dynamic fields
Downloads
120
Readme
Dependency Parser
A TypeScript-based dependency parser for Stepper.io integrations that analyzes function code to detect dependencies on context properties (context.input or context.auth).
Features
- Static dependency analysis: Detects which context properties are accessed in function code
- Import following: Recursively analyzes imported helper functions to discover nested dependencies
- Destructuring support: Handles object destructuring patterns
- Aliasing support: Tracks variables that point to context properties
- Limitation detection: Identifies patterns that prevent complete static analysis
- Caching: Optimizes performance for repeated analyses
- TypeScript-first: Built with full TypeScript support
Installation
pnpm install @stepper-io/dependency-parserBasic Usage
Simple Dependency Detection
import { parseDependencies } from '@stepper-io/dependency-parser';
const functionCode = `async (context) => {
if (!context.input.board_id) return [];
const { group_id } = context.input;
return [board_id, group_id];
}`;
const result = parseDependencies(functionCode);
console.log(result.dependencies); // ['board_id', 'group_id']Import Following
Enable import following to analyze helper functions in separate files:
import fs from 'fs';
import { parseDependencies } from '@stepper-io/dependency-parser';
const actionCode = `
import { getItems } from './api';
export const action = async (context) => {
if (!context.input.board_id) return [];
return getItems(context, context.input.board_id);
};
`;
const result = parseDependencies(actionCode, {
followImports: true,
currentFilePath: '/app/actions/updateItem.ts',
fileSystem: {
readFileSync: (path) => fs.readFileSync(path, 'utf-8'),
existsSync: fs.existsSync,
},
maxImportDepth: 3, // Default: 3
});
console.log(result.dependencies); // ['board_id', 'column_id']
// ^ Includes dependencies from both the action and getItems()
console.log(result.resolvedFunctions); // ['getItems']
console.log(result.analysisDepth); // 1API Reference
parseDependencies(functionCode, options?)
Analyzes a function to detect dependencies on context properties.
Parameters:
functionCode(string): The function code to analyzeoptions(ParserOptions, optional): Configuration options
Returns: ParserResult
ParserOptions
interface ParserOptions {
// Basic options
includeMessages?: boolean; // Include human-readable limitation messages (default: true)
contextProperty?: 'input' | 'auth'; // Which context property to analyze (default: 'input')
// Import following options (NEW)
followImports?: boolean; // Enable import following (default: false)
maxImportDepth?: number; // Maximum recursion depth (default: 3)
currentFilePath?: string; // Required when followImports is true
fileSystem?: FileSystemInterface; // Required when followImports is true
_cache?: ImportAnalysisCache; // Optional cache instance
}
interface FileSystemInterface {
readFileSync(path: string, encoding: 'utf-8'): string;
existsSync(path: string): boolean;
}ParserResult
interface ParserResult {
dependencies: string[]; // Detected dependencies
limitations: LimitationType[]; // Analysis limitations
limitationMessages?: string[]; // Human-readable messages
unresolvedFunctions?: string[]; // Functions that couldn't be analyzed
// Import following results (NEW)
resolvedFunctions?: string[]; // Successfully analyzed imports
analysisDepth?: number; // Maximum recursion depth reached
}
type LimitationType =
| 'DYNAMIC_PROPERTY_ACCESS' // e.g., context.input[key]
| 'SPREAD_OPERATOR' // e.g., { ...rest } = context.input
| 'UNRESOLVED_FUNCTION_CALL' // Function with context not analyzed
| 'PARSE_ERROR'; // Invalid syntaxImport Following
The import following feature recursively analyzes imported helper functions to discover nested dependencies that would otherwise be missed.
How It Works
Module Resolution: Resolves import paths to absolute file paths
- Supports relative imports:
./api,../utils/helper - Tries multiple extensions:
.ts,.tsx,.js,.jsx - Skips external packages:
@stepper-io/core,lodash, etc.
- Supports relative imports:
Function Extraction: Extracts exported function definitions
- Arrow functions:
export const fn = async () => {...} - Function declarations:
export function fn() {...}
- Arrow functions:
Parameter Mapping: Maps function call arguments to parameters
getItems(context)→ parametercontextpoints to contextvalidate(context.input)→ parameterinputpoints to context.input
Recursive Analysis: Analyzes function bodies recursively
- Respects
maxImportDepthlimit - Detects and breaks circular dependencies
- Caches results for performance
- Respects
Example: Monday.com Integration
// actions/updateItem.ts
import { getColumns, mondayGraphQL } from './api';
export const action = async (context) => {
const { board_id } = context.input;
if (!board_id) return [];
const columns = await getColumns(context, board_id);
return columns;
};
// api.ts
import { mondayGraphQL } from './graphql';
export const getColumns = async (context, board_id) => {
const data = await mondayGraphQL(context, query, { board_id: [board_id] });
return data.boards[0].columns.filter((c) => c.id === context.input.column_id);
// ^^^ Nested dependency!
};Without import following:
const result = parseDependencies(actionCode);
// dependencies: ['board_id']
// unresolvedFunctions: ['getColumns']
// limitations: ['UNRESOLVED_FUNCTION_CALL']With import following:
const result = parseDependencies(actionCode, {
followImports: true,
currentFilePath: '/app/actions/updateItem.ts',
fileSystem: { readFileSync: fs.readFileSync, existsSync: fs.existsSync },
});
// dependencies: ['board_id', 'column_id'] ← Found the nested dependency!
// resolvedFunctions: ['getColumns', 'mondayGraphQL']
// unresolvedFunctions: []
// analysisDepth: 2Performance Optimization
Use caching for better performance when analyzing multiple actions:
import { ImportAnalysisCache } from '@stepper-io/dependency-parser';
const cache = new ImportAnalysisCache();
// Analyze multiple actions with shared cache
for (const action of actions) {
const result = parseDependencies(action.code, {
followImports: true,
currentFilePath: action.filePath,
fileSystem: fs,
_cache: cache,
});
}
console.log(`Cache size: ${cache.size()} entries`);Performance Characteristics:
- Cold analysis (no cache): < 200ms per action
- Warm analysis (with cache): < 50ms per action
- Memory usage: < 50MB for 100 integrations
Depth Limiting
Control how deep the parser follows imports:
const result = parseDependencies(code, {
followImports: true,
maxImportDepth: 2, // Stop after 2 levels
currentFilePath: '/app/action.ts',
fileSystem: fs,
});
// If action → helper1 → helper2 → helper3:
// - helper1 and helper2 are analyzed (depth 1, 2)
// - helper3 is marked as UNRESOLVED_FUNCTION_CALL (depth 3 > limit)Circular Dependency Handling
The parser automatically detects and breaks circular imports:
// a.ts
import { funcB } from './b';
export const funcA = (context) => {
const val = context.input.fieldA;
return funcB(context);
};
// b.ts
import { funcA } from './a';
export const funcB = (context) => {
const val = context.input.fieldB;
return funcA(context); // ← Circular!
};The parser will analyze both functions once, avoiding infinite loops:
// dependencies: ['fieldA', 'fieldB']
// resolvedFunctions: ['funcA', 'funcB']Other API Functions
analyzeFieldDependencies(fields, options?)
Analyzes an array of Stepper input fields (supports both static and dynamic fields).
buildAppDependenciesMap(app, options?)
Builds a complete dependency map for a Stepper app (all actions, triggers, and connections).
import { buildAppDependenciesMap } from '@stepper-io/dependency-parser';
const dependencyMap = buildAppDependenciesMap(myApp, {
followImports: true,
currentFilePath: '/app/index.ts',
fileSystem: fs,
});
console.log(dependencyMap.actions['create_item'].dependencies);
console.log(dependencyMap.triggers['new_item'].dependencies);Supported Patterns
Property Access
context.input.board_id;
context.auth.api_key;Destructuring
const { board_id, group_id } = context.input;
const { input } = context;
const value = input.item_id;Aliasing
const { input } = context;
const boardId = input.board_id;Optional Chaining
context.input?.optional_field;Limitations
The parser detects but cannot fully analyze these patterns:
Dynamic Property Access
const key = 'board_id';
context.input[key]; // Cannot determine staticallySpread Operator
const { board_id, ...rest } = context.input; // Cannot enumerate restUnresolved Function Calls (without import following)
helper(context); // Cannot analyze what helper does