| Insilode from an endpoint | Insilode at the cursor | Database schema cards | |
|---|---|---|---|
| C# / .NET | ✓ ASP.NET (Roslyn, incl. MediatR and DI) from a .sln/.slnx, or a .csproj when there is no solution; needs the .NET 10 SDK |
✓ | ✓ EF Core |
| Java | ✓ Spring, JAX-RS, Servlets, Axis2, JSF, plus scheduled jobs and message listeners | ✓ | ✓ JPA entities and Spring Data repositories; JDBC (raw SQL) in progress |
| Python | ✓ FastAPI, Flask, Django / DRF | ✓ | ✓ Django ORM, SQLAlchemy |
| JavaScript / TypeScript | ✓ Express, NestJS, Next.js (app and pages routers) | ✓ | ✓ Prisma and TypeORM |
| Go | ✓ Gin, Echo, Fiber, chi, net/http | ✓ | not yet |
| PHP | ✓ Laravel routes and resources, Symfony #[Route] |
✓ | not yet |
| Ruby | ✓ Rails routes, resources, Sinatra |
✓ | not yet |
| Rust | ✓ axum, actix-web, Rocket | ✓ | not yet |
| Kotlin, Swift and others | not yet | only if you install that language's extension (Open VSX) and its server supports call hierarchy; not tested here | not yet |
How it resolves a flow. Entry points (routes, jobs, listeners) and most calls come from a build-free tree-sitter index, so a project opens instantly and needs no restore or compile. Calls the index cannot resolve, or only guessed by name, are asked to the language server of that file (go-to-definition, then go-to-implementation for interface methods) when one is running; no server, or no answer within 1.5 s, leaves the flow as the index drew it. Turn it off with flow.lspResolution. C# uses the Roslyn analyzer in analyzer/ (it needs the project restored, and the .NET 10 SDK to run). Go, PHP, Ruby and Rust were checked on small sample projects only; the linking is name-based within a package or directory, so large codebases lean on their language server for the rest.
What is indexed. Every file of the workspace folder in an indexed language, except what git ignores (build output, generated code, a vendored checkout: the same files git status leaves out) and dependency and cache folders such as node_modules, .venv and dist. A folder that is not a git repository is indexed whole, minus those folders.
Calls between services. An HTTP client call whose path matches an endpoint in the same workspace (fetch, axios and this.http in JavaScript/TypeScript; requests, httpx and sessions in Python; in Java a RestTemplate, WebClient or RestClient by its declared type whatever the variable is called, RestTemplate's own methods such as postForObject, and .uri(...).retrieve() chains; net/http, NewRequest and resty in Go; the Laravel Http facade, Guzzle and Symfony HttpClient in PHP; HTTParty, RestClient, Faraday and Net::HTTP in Ruby; reqwest in Rust) continues the flow into that endpoint's handler, drawn as a dashed HTTP? edge. It is always a guess: the path is matched by its written segments (parameters, interpolations and concatenated variables match any one segment, a base URL held in a variable matches the end of the route), and which service actually answers is not known statically.
Messages between services. A message sent to a topic or queue that a listener of the same workspace consumes continues the flow into that listener, drawn as a dashed topic? edge: in Java a Spring KafkaTemplate, RabbitTemplate, JmsTemplate, StreamBridge or SQS template (by declared type, else by field name) reaching @KafkaListener, @RabbitListener, @JmsListener, @SqsListener or @StreamListener; in Python a Celery task.delay() / apply_async() / send_task("name") reaching the @shared_task / @app.task, and a Kafka producer's send / produce; in JavaScript/TypeScript a kafkajs producer.send({ topic }) and a NestJS ClientProxy.emit / send reaching @EventPattern / @MessagePattern. Destinations match by their exact name, read through string constants; a name not written out in full (a ${app.topic} placeholder, an f-string, a concatenation) links nowhere. Like HTTP links it is always a guess, since brokers route by configuration the code does not show. Go, PHP, Ruby and Rust producers are not read yet.
Tables and entities
A change to an entity names its table and the endpoints that use it. Change impact follows calls, so editing a shared method shows every endpoint that reaches it. An entity or model class has no callers: what depends on it is the table it maps to. When an edit lands in an entity (or in the ORM configuration that names its table), Insilode resolves the table with the schema readers (JPA @Entity / @Table, SQLAlchemy __tablename__, Django Meta.db_table, TypeORM @Entity, Prisma model / @@map, and EF Core [Table] / ToTable in an IEntityTypeConfiguration), then lists the endpoints whose flow reaches a database card for that table, split into the ones that write it and the ones that read it. It shows up in four places:
- Change set (
open_diffwithflow: true, or Insilode: Show Git Changes in Flow): a root item "Order changed → table orders · 4 endpoints read or write it", and the flow of each of those endpoints, cut down to the path that reaches the database card, which is marked as changed. impactwithtableorentity: the same endpoints as text, split into writes and reads, each with the method and card it goes through.flow_text:~tableafter a database line whose table name is a guess.- Agents dock: the turn-impact line adds "and table orders (4 endpoints)".
The list is built the first time it is asked, from the code index and, when it is already loaded, the C# analyzer; it is cached and starts over when the index or the solution changes. A big repository answers in parts: the answer says how many entry points were walked and a second question continues. Asking never starts the C# analyzer: with it not loaded, C# entities and endpoints are named as not checked ("partial").
Certain, guessed, unknown. A link is certain when the table name comes from ORM metadata and the endpoint's database card was resolved through a typed repository or DbSet. It is a guess when the name is not written in the code (a JPA entity without @Table, a Django model without db_table, a bare TypeORM @Entity(), an EF entity without [Table] or ToTable, which EF names after its DbSet) or when the table comes from a raw SQL string; the canvas draws the edge dashed with a table? tag, like HTTP? and topic?. What the answer cannot see is always listed as not checked: dynamic SQL, views, stored procedures and triggers, other services or reports that read the same database, and endpoints in languages without schema cards (Go, PHP, Ruby and Rust have none yet).
A changed migration works like a changed entity. Edit a migration and Insilode reads what it does to which tables, then lists the endpoints that read or write each one, the way it does for an entity. The change set gets a root item "migration 0007_orders_key alters orders (adds key, drops legacy_ref) · 4 endpoints read or write it", and the database cards of those endpoints are marked with what the migration adds and drops. impact takes the migration file itself (file without method or line), and so does an entity file. A column the migration drops or renames away is searched in the code: places that mention it by name, as written, in camel case or in Pascal case, in files that also know the table or its entity, are listed as "may still use it". That is a guess: it finds a name, not a use.
| Migration | Read from | What is read |
|---|---|---|
EF Core (Migrations/*.cs) |
the C# analyzer, when it is loaded | migrationBuilder.CreateTable, AddColumn, DropColumn, RenameColumn, AlterColumn, RenameTable, DropTable, CreateIndex, DropIndex, foreign and primary keys, and Sql("...") through the DDL reader; Up, not Down |
Flyway, Prisma (migration.sql) and plain SQL |
the text of any .sql file |
CREATE, ALTER and DROP TABLE, CREATE and DROP INDEX, RENAME (PostgreSQL, MySQL and SQL Server forms), also inside Liquibase <sql>, Alembic op.execute, Django RunSQL and TypeORM queryRunner.query |
| Liquibase | XML, YAML and JSON change logs | createTable, addColumn, dropColumn, renameColumn, renameTable, dropTable, createIndex, constraints; not rollback |
| Alembic | *.py with revision and op. calls |
create_table, add_column, drop_column, alter_column, rename_table, drop_table, create_index, batch_alter_table; upgrade, not downgrade |
| Django | migrations/*.py |
CreateModel, AddField, RemoveField, AlterField, RenameField, RenameModel, DeleteModel, AddIndex, AlterModelTable; the model is resolved to its table through the project's models, and by Django's app_model rule (a guess) when the model is not found |
| TypeORM | *.ts with queryRunner |
addColumn, dropColumn, renameColumn, createTable, dropTable, createIndex, raw query; up, not down |
| Rails | db/migrate/*.rb |
create_table, add_column, remove_column, rename_column, drop_table, add_index, add_reference: table names only, said so in the answer, because Ruby has no schema cards to match columns to code |
A migration is only read when it is one of the files that changed; Insilode does not scan every migration of the project, and it does not read a migration that builds its table names at run time (it says so). Data migrations (RunPython, UPDATE statements) are not read.
Entities that share a name. Two services can both have an Order. A repository, session query or client call resolves the name only as far as the code spells it out: the same file, an import that leads to the declaration, the Java package, or, in C#, the DbSet's declared type. Then each endpoint reaches its own table and the link is certain. Where the code does not say (a star import or no import in Python and TypeScript, so the index falls back to the same folder or the first match, and Prisma, where which schema a client was generated from is not in the call) the card is drawn as a guess. EF's modelBuilder.Entity<T>() in a DbContext (ToTable, HasKey, Property(...).HasColumnName, written as a chain or in a lambda) is read like an IEntityTypeConfiguration, and an edit to that statement changes that entity.