Parser Extractors
Complete reference for all 37 extractors across 13 languages — with code examples showing exactly what Nomik detects.
Nomik's parser uses Tree-sitter to convert source code into AST nodes, then runs 37 extractors to produce typed graph nodes and edges. Config files are parsed via a dedicated regex-based config-file-parser.ts without tree-sitter. All TypeScript/JavaScript extractors are import-aware — they resolve receiver variables from actual imports, not hardcoded names.
Supported Languages
Code Languages (Tree-sitter)
| Language | Grammar | Extensions | Scope |
|---|---|---|---|
| TypeScript | tree-sitter-typescript | .ts, .tsx | All 37 extractors |
| JavaScript | tree-sitter-typescript | .js, .jsx, .mjs, .cjs | All 37 extractors |
| Python | tree-sitter-python | .py, .pyw | Functions, classes, imports, calls + runtime extractors |
| Rust | tree-sitter-rust | .rs | Functions, structs/enums/traits, use, calls |
| Markdown | Custom regex parser | .md | Sections (h1-h6 headings) |
| SQL | Custom regex parser | .sql | CREATE TABLE, ALTER TABLE, column definitions |
| C#/Python migrations | Custom regex parser | .cs, .py | Entity Framework, Django, Alembic migration schema |
Config File Languages (regex via config-file-parser.ts)
| Language | Extensions | What's Extracted |
|---|---|---|
| YAML | .yml, .yaml | Docker Compose services, K8s manifests, GitHub Actions jobs, GitLab CI stages, CloudFormation resources, Prometheus alert rules, OpenAPI specs |
| Terraform | .tf, .tfvars | Resources, variables, modules, outputs |
| GraphQL | .graphql, .gql | Types, queries, mutations, subscriptions |
| Dockerfile | Dockerfile, Dockerfile.* | Base images, exposed ports, build stages |
| .env | .env, .env.* | Environment variable definitions (deduplicated with process.env references in code) |
| JSON config | package.json | Dependencies, devDependencies, OpenAPI specs, Grafana dashboards |
Config files are automatically discovered during nomik scan. No extra configuration needed — Nomik detects file types by name and extension and routes them to the correct extractor.
Code Extractors
Functions
// → FunctionNode: name="processPayment", async=true, exported=true,
// params="orderId: string, amount: number", returnType="Promise<void>",
// startLine=1, endLine=5, bodyHash="sha256:a1b2c3..."
export async function processPayment(orderId: string, amount: number): Promise<void> {
const order = await getOrder(orderId);
await createCharge(order, amount);
eventBus.emit('payment.completed', { orderId });
}Extracted properties: name, params, returnType, async, exported, generator, decorators, bodyHash (SHA-256, whitespace-normalized), startLine, endLine, filePath.
Classes
// → ClassNode: name="PaymentService", abstract=false
// → EXTENDS edge to BaseService
// → IMPLEMENTS edge to IPaymentProvider
export class PaymentService extends BaseService implements IPaymentProvider {
async process(orderId: string) { /* ... */ }
}Imports and Exports
// → ImportInfo: source="@prisma/client", specifiers=["PrismaClient"], isDefault=false
import { PrismaClient } from '@prisma/client';
// → ImportInfo: source="./payment", specifiers=["processPayment"], isDynamic=false
import { processPayment } from './payment';
// → ExportInfo: re-export resolution, barrel file detection
export { validateUser } from './auth';
export * from './utils';Calls (with Confidence Scoring)
function checkout(orderId: string) {
// → CALLS edge: checkout → validateUser (confidence: 1.0, same-file)
validateUser(req.user);
// → CALLS edge: checkout → processPayment (confidence: 0.95, import-resolved)
processPayment(orderId, amount);
// → CALLS edge: checkout → orderService.getOrder (confidence: 0.95, obj.method())
const order = orderService.getOrder(orderId);
// → CALLS edge: checkout → sendNotification (confidence: 0.60, global-filtered)
sendNotification(orderId);
}| Resolution | Confidence | Description |
|---|---|---|
| Same-file | 1.0 | Caller and callee in the same file |
| File-context | 0.90 | Resolved within file scope |
| Import-resolved | 0.95 | Resolved via import statement |
| Global-filtered | 0.60 | Resolved via global function name match (single match) |
API Extractors
Routes
// → RouteNode: method="POST", path="/api/checkout", handler="checkoutHandler"
// → HANDLES edge: Route → checkoutHandler
app.post('/api/checkout', checkoutHandler);
// → RouteNode: method="GET", path="/api/users/:id"
router.get('/api/users/:id', (req, res) => { /* ... */ });
// NestJS decorators
// → RouteNode: method="POST", path="/payments", handler="create"
@Post('/payments')
async create(@Body() dto: CreatePaymentDto) { /* ... */ }Supported: Express, Fastify, NestJS, Koa, Hapi, Next.js, Nuxt, tRPC, gRPC, GraphQL resolvers.
External API Calls
import axios from 'axios';
import { ofetch } from 'ofetch';
// → ExternalAPINode: name="stripe", url="https://api.stripe.com/v1/charges"
// → CALLS_EXTERNAL edge: processPayment → stripe
await axios.post('https://api.stripe.com/v1/charges', data);
// → CALLS_EXTERNAL edge: fetchUsers → jsonplaceholder
const users = await ofetch('https://jsonplaceholder.typicode.com/users');
// Built-in globals always detected
// → CALLS_EXTERNAL edge: getConfig → config-api
await fetch('/api/config');Import-aware detection: axios, ky, got, node-fetch, ofetch, undici, superagent, fetch(), $fetch().
Data Extractors
Database Operations
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// → WRITES_TO edge: createUser → DBTable:user (operation: INSERT)
await prisma.user.create({ data: { name, email } });
// → READS_FROM edge: getUsers → DBTable:user (operation: SELECT)
const users = await prisma.user.findMany({ where: { active: true } });import { createClient } from '@supabase/supabase-js';
const supabase = createClient(url, key);
// → WRITES_TO edge (correctly classified as INSERT despite terminal .select())
await supabase.from('orders').insert({ userId, total }).select();
// → READS_FROM edge
const { data } = await supabase.from('products').select('*').eq('active', true);Supported ORMs: Prisma, Supabase, Knex, TypeORM, Drizzle, pg, mysql2, raw SQL.
Database Schema
-- → DBTableNode: name="users"
-- → DBColumnNode: name="id", type="SERIAL", primaryKey=true
-- → DBColumnNode: name="email", type="VARCHAR(255)", nullable=false
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);# → DBTableNode: name="orders"
# → DBColumnNode: name="total", type="DecimalField"
class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Order',
fields=[
('id', models.AutoField(primary_key=True)),
('total', models.DecimalField(max_digits=10)),
],
),
]Redis
import Redis from 'ioredis';
const redis = new Redis();
// → WRITES_TO edge: cacheUser → DBTable:user:* (schema=redis, operation=WRITE)
await redis.set(`user:${userId}`, JSON.stringify(user));
// → READS_FROM edge: getCachedUser → DBTable:user:* (schema=redis, operation=READ)
const cached = await redis.get(`user:${userId}`);Infrastructure Extractors
Queue Jobs
import { Queue, Worker } from 'bullmq';
const emailQueue = new Queue('email-notifications');
// → PRODUCES_JOB edge: sendWelcome → QueueJob:email-notifications
await emailQueue.add('welcome', { userId, template: 'welcome' });
// → CONSUMES_JOB edge: emailWorker → QueueJob:email-notifications
const worker = new Worker('email-notifications', async (job) => {
await sendEmail(job.data);
});Supported: Bull, BullMQ, Bee-Queue, Agenda, pg-boss.
Metrics
import { Counter, Histogram } from 'prom-client';
// → MetricNode: name="http_requests_total", type="Counter"
const requestCounter = new Counter({
name: 'http_requests_total',
help: 'Total HTTP requests',
labelNames: ['method', 'status'],
});
// → USES_METRIC edge: handleRequest → Metric:http_requests_total
function handleRequest(req: Request) {
requestCounter.labels(req.method, '200').inc();
}Tracing (OpenTelemetry)
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('payment-service');
// → SpanNode: name="process-payment"
// → STARTS_SPAN edge: processPayment → Span:process-payment
function processPayment(orderId: string) {
tracer.startActiveSpan('process-payment', async (span) => {
// ... payment logic
span.end();
});
}Supported: @opentelemetry/api, dd-trace (Datadog), @sentry/node.
Message Brokers
import { Kafka } from 'kafkajs';
const kafka = new Kafka({ brokers: ['localhost:9092'] });
const producer = kafka.producer();
// → PRODUCES_MESSAGE edge: publishOrder → Topic:order-events
await producer.send({ topic: 'order-events', messages: [{ value: JSON.stringify(order) }] });
const consumer = kafka.consumer({ groupId: 'order-processor' });
// → CONSUMES_MESSAGE edge: orderHandler → Topic:order-events
await consumer.subscribe({ topic: 'order-events' });Supported: KafkaJS, amqplib (RabbitMQ), NATS, AWS SQS/SNS, Google Pub/Sub.
Events and WebSockets
// → EventNode: name="payment.completed"
// → EMITS edge: processPayment → Event:payment.completed
eventBus.emit('payment.completed', { orderId });
// → LISTENS_TO edge: sendReceipt → Event:payment.completed
eventBus.on('payment.completed', sendReceipt);
// Socket.io room/namespace detection
// → EventNode with room="admin-room"
socket.to('admin-room').emit('notification', data);
io.of('/admin').on('connection', handler);Cron Jobs
import cron from 'node-cron';
// → CronJobNode: name="cleanup", schedule="0 0 * * *"
// → SCHEDULES edge: cleanupJob → CronJob:cleanup
cron.schedule('0 0 * * *', async () => {
await cleanupExpiredSessions();
});
// NestJS @Cron decorator
// → CronJobNode: name="handleDailyReport", schedule="0 8 * * *"
@Cron('0 8 * * *')
async handleDailyReport() { /* ... */ }Config Extractors
Docker and Kubernetes
# → Config metadata: baseImage="node:20-alpine", exposedPort=3000
FROM node:20-alpine
EXPOSE 3000
CMD ["node", "dist/index.js"]# → Service nodes with port mappings and dependencies
services:
api:
build: .
ports: ["3000:3000"]
depends_on: [neo4j, redis]
neo4j:
image: neo4j:5-communityCI/CD
# → CronJobNode for each job, steps with action references
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
quality:
runs-on: ubuntu-latest
steps:
- run: nomik ciTerraform
# → ClassNode: name="aws_instance.api", type="resource"
resource "aws_instance" "api" {
ami = var.ami_id
instance_type = "t3.medium"
}
# → EnvVarNode: name="ami_id"
variable "ami_id" {
type = string
}Environment Variables
// → EnvVarNode: name="DATABASE_URL", required=true
const dbUrl = process.env.DATABASE_URL!;
// → EnvVarNode: name="PORT", defaultValue="3000"
const port = process.env.PORT ?? '3000';
// → EnvVarNode: name="LOG_LEVEL", defaultValue="info"
const logLevel = process.env['LOG_LEVEL'] || 'info';# → EnvVarNode: name="SECRET_KEY"
secret = os.environ['SECRET_KEY']
# → EnvVarNode: name="DEBUG", defaultValue="false"
debug = os.getenv('DEBUG', 'false')Security Extractors
Secret Detection
// → SecurityIssueNode: type="aws_key", severity="critical"
const awsKey = 'AKIAIOSFODNN7EXAMPLE';
// → SecurityIssueNode: type="github_token", severity="high"
const token = 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
// → SecurityIssueNode: type="stripe_key", severity="critical"
const stripe = 'sk_live_xxxxxxxxxxxxxxxxxxxx';
// → SecurityIssueNode: type="private_key", severity="critical"
const key = '-----BEGIN RSA PRIVATE KEY-----';
// → SecurityIssueNode: type="basic_auth_url", severity="high"
const url = 'https://admin:password@api.example.com';Skips: comments, test files, mock data, placeholder values (xxx, example, test).
Feature Flags
import { LDClient } from 'launchdarkly-node-server-sdk';
// → EnvVarNode: name="new-checkout-flow", kind="feature_flag"
const showNewCheckout = await ldClient.variation('new-checkout-flow', user, false);
// Also detects env-based flags
// → EnvVarNode: name="FEATURE_NEW_CHECKOUT"
if (process.env.FEATURE_NEW_CHECKOUT === 'true') { /* ... */ }Python Extractors
# Functions with typed parameters (self/cls excluded)
# → FunctionNode: name="process_order", async=true, params="order_id: str, db: Session"
async def process_order(order_id: str, db: Session) -> Order:
# Celery tasks
# → PRODUCES_JOB edge → QueueJob:send_notification
send_notification.delay(order_id)
# Redis operations
# → WRITES_TO edge → DBTable:order (schema=redis)
redis_client.set(f"order:{order_id}", json.dumps(data))
# Prometheus metrics
# → USES_METRIC edge → Metric:orders_processed
orders_processed.inc()
# OpenTelemetry spans
# → STARTS_SPAN edge → Span:process_order
with tracer.start_span("process_order"):
pass
# Message brokers (confluent_kafka, pika, nats)
# → PRODUCES_MESSAGE edge → Topic:order-events
producer.produce('order-events', value=json.dumps(event))Rust Extractors
// → FunctionNode: name="handle_request", async=true, visibility="pub"
pub async fn handle_request(req: Request) -> Response {
let user = db::get_user(req.user_id).await;
let response = process(user).await;
response
}
// → ClassNode: name="PaymentService" (struct)
pub struct PaymentService {
db: DatabasePool,
stripe: StripeClient,
}
// → ClassNode: name="Payment" (enum)
pub enum Payment {
CreditCard { number: String },
BankTransfer { iban: String },
}
// → ClassNode: name="Processable" (trait, abstract=true)
pub trait Processable {
async fn process(&self) -> Result<(), Error>;
}
// → ImportInfo: use declarations
use crate::services::payment::PaymentService;
use std::collections::HashMap;Cross-File Resolution
After per-file extraction, the parser resolves cross-file relationships:
- Cross-file CALLS — Multi-map resolution (
Map<string, string[]>) handles duplicate function names. Two-layer defense for name collisions: local shadow check + importedFileIds filter. Method calls (obj.method()) skip shadow check since the receiver disambiguates - Barrel re-exports —
export * fromandexport { x } fromare followed transitively via BFS to resolve all re-exported symbols - Intra-file CALLS — Local function calls, variable array aliases, declaration aliases
- Route handling —
HANDLESedges,EXTENDS/IMPLEMENTS, framework entry points (Next.js, Nuxt) - Path alias resolution — tsconfig.json/jsconfig.json path alias resolution (
@/*,~/*) for monorepos
Graph Schema
Complete reference for Nomik's Neo4j graph schema — 17 node types, 19 edge types, properties, constraints, indexes, and Cypher query examples.
Visualization Dashboard
Interactive 2D and 3D graph exploration with all 17 node types, 10 edge types, search, filtering, impact overlay, health stats, and query caching — powered by Cytoscape.js and Three.js.