proschi 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.cjs +640 -218
- package/dist/render.cjs +159 -159
- package/dist/server.cjs +595 -219
- package/grammar/proschi.tmLanguage.json +1 -1
- package/package.json +2 -2
- package/schema/proschi-diagram.schema.json +138 -6
package/dist/server.cjs
CHANGED
|
@@ -25299,125 +25299,237 @@ var componentCatalog = [
|
|
|
25299
25299
|
{ type: "shape", techStack: "Diamond", category: "Generic Shapes", searchTerms: "diamond rhombus shape decision" },
|
|
25300
25300
|
{ type: "shape", techStack: "Cylinder", category: "Generic Shapes", searchTerms: "cylinder shape storage" },
|
|
25301
25301
|
{ type: "shape", techStack: "Cloud", category: "Generic Shapes", searchTerms: "cloud shape" },
|
|
25302
|
-
{ type: "shape", techStack: "Actor", category: "Generic Shapes", searchTerms: "actor user person human shape" },
|
|
25303
|
-
{ type: "shape", techStack: "
|
|
25302
|
+
{ type: "shape", techStack: "Actor", category: "Generic Shapes", searchTerms: "actor user person human shape", aliases: ["User", "Client", "Customer", "Person"] },
|
|
25303
|
+
{ type: "shape", techStack: "Browser", category: "Generic Shapes", searchTerms: "browser web client user", aliases: ["Web Browser", "Web Client"] },
|
|
25304
|
+
{ type: "shape", techStack: "Mobile App", category: "Generic Shapes", searchTerms: "mobile app ios android client user", aliases: ["Mobile", "Mobile Client", "iOS", "iOS App", "Android", "Android App"] },
|
|
25305
|
+
{ type: "shape", techStack: "Note", category: "Generic Shapes", searchTerms: "note document comment annotation", kind: "other" },
|
|
25306
|
+
// Generic components: one per kind, for a node whose product does not matter or is not listed.
|
|
25307
|
+
{ type: "service", techStack: "Service", category: "Generic Components", searchTerms: "service microservice backend server application", aliases: ["Microservice", "Backend", "Backend Service", "App Server", "Application Server", "Server", "API Server"] },
|
|
25308
|
+
{ type: "service", techStack: "Worker", category: "Generic Components", searchTerms: "worker background job consumer processor", aliases: ["Background Worker", "Job Worker", "Consumer", "Job Runner"] },
|
|
25309
|
+
{ type: "database", techStack: "Database", category: "Generic Components", searchTerms: "database relational sql db", aliases: ["DB", "RDBMS", "SQL Database", "Relational Database", "SQL"] },
|
|
25310
|
+
{ type: "database", techStack: "NoSQL Database", category: "Generic Components", searchTerms: "nosql key value document wide column database", profile: "nosql", aliases: ["NoSQL", "NoSQL DB", "Key-Value Store", "KV Store", "Document Store", "Document Database", "Wide-Column Store"] },
|
|
25311
|
+
{ type: "cache", techStack: "Cache", category: "Generic Components", searchTerms: "cache in-memory key value", aliases: ["In-Memory Cache", "Distributed Cache"] },
|
|
25312
|
+
{ type: "queue", techStack: "Message Queue", category: "Generic Components", searchTerms: "queue message broker topic stream event bus", aliases: ["Queue", "Message Broker", "Broker", "Event Bus", "Topic", "Event Stream", "Stream"] },
|
|
25313
|
+
{ type: "storage", techStack: "Object Storage", category: "Generic Components", searchTerms: "object storage blob bucket files", aliases: ["Object Store", "Blob Store", "Blob Storage", "Bucket", "Storage", "File Storage"] },
|
|
25314
|
+
{ type: "cdn", techStack: "CDN", category: "Generic Components", searchTerms: "cdn content delivery network edge cache", kind: "cdn", aliases: ["Content Delivery Network", "Edge Cache"] },
|
|
25315
|
+
{ type: "cdn", techStack: "Load Balancer", category: "Generic Components", searchTerms: "load balancer lb reverse proxy", kind: "loadbalancer", aliases: ["LB", "Reverse Proxy", "L7 Load Balancer", "L4 Load Balancer"] },
|
|
25316
|
+
{ type: "cdn", techStack: "API Gateway", category: "Generic Components", searchTerms: "api gateway", kind: "gateway", aliases: ["Gateway", "Edge Gateway"] },
|
|
25317
|
+
{ type: "cdn", techStack: "DNS", category: "Generic Components", searchTerms: "dns domain name system", kind: "dns", aliases: ["Domain Name System", "DNS Server"] },
|
|
25318
|
+
{ type: "cdn", techStack: "WAF", category: "Generic Components", searchTerms: "waf web application firewall edge", kind: "edge", aliases: ["Web Application Firewall", "Firewall"] },
|
|
25319
|
+
{ type: "serverless", techStack: "Function", category: "Generic Components", searchTerms: "function serverless faas", aliases: ["Serverless Function", "Serverless", "FaaS", "Cloud Function"] },
|
|
25320
|
+
{ type: "database", techStack: "Search Engine", category: "Generic Components", searchTerms: "search engine index full text", kind: "search", aliases: ["Search", "Search Index", "Full-Text Search", "Search Service"] },
|
|
25321
|
+
{ type: "database", techStack: "Data Warehouse", category: "Generic Components", searchTerms: "data warehouse lake olap analytics", kind: "analytics", aliases: ["Warehouse", "Data Lake", "OLAP", "Analytics DB", "Analytics Database", "Time Series DB", "Time Series Database", "TSDB"] },
|
|
25304
25322
|
// Services
|
|
25305
|
-
{ type: "service", techStack: "REST API", category: "Services", searchTerms: "rest api http service endpoint" },
|
|
25306
|
-
{ type: "service", techStack: "GraphQL", category: "Services", searchTerms: "graphql api service query" },
|
|
25307
|
-
{ type: "service", techStack: "gRPC", category: "Services", searchTerms: "grpc rpc service" },
|
|
25308
|
-
{ type: "service", techStack: "WebSocket", category: "Services", searchTerms: "websocket ws realtime service" },
|
|
25309
|
-
{ type: "service", techStack: "SOAP API", category: "Services", searchTerms: "soap api xml service" },
|
|
25323
|
+
{ type: "service", techStack: "REST API", category: "Services", searchTerms: "rest api http service endpoint", aliases: ["REST", "API", "HTTP API", "Web API", "REST Service"] },
|
|
25324
|
+
{ type: "service", techStack: "GraphQL", category: "Services", searchTerms: "graphql api service query", aliases: ["GraphQL API", "Apollo"] },
|
|
25325
|
+
{ type: "service", techStack: "gRPC", category: "Services", searchTerms: "grpc rpc service", aliases: ["gRPC Service", "RPC"] },
|
|
25326
|
+
{ type: "service", techStack: "WebSocket", category: "Services", searchTerms: "websocket ws realtime service", aliases: ["WebSockets", "WS", "Socket.IO", "WebSocket Server"] },
|
|
25327
|
+
{ type: "service", techStack: "SOAP API", category: "Services", searchTerms: "soap api xml service", aliases: ["SOAP"] },
|
|
25328
|
+
// Languages and frameworks: a service written in it.
|
|
25329
|
+
{ type: "service", techStack: "Spring Boot", category: "Languages & Frameworks", searchTerms: "spring boot java kotlin service", aliases: ["Spring", "Spring Framework", "Spring WebFlux"] },
|
|
25330
|
+
{ type: "service", techStack: "Kotlin", category: "Languages & Frameworks", searchTerms: "kotlin ktor jvm service", aliases: ["Ktor"] },
|
|
25331
|
+
{ type: "service", techStack: "Java", category: "Languages & Frameworks", searchTerms: "java jvm service", aliases: ["JVM", "Quarkus", "Micronaut"] },
|
|
25332
|
+
{ type: "service", techStack: "Go", category: "Languages & Frameworks", searchTerms: "go golang service", aliases: ["Golang"] },
|
|
25333
|
+
{ type: "service", techStack: "Node.js", category: "Languages & Frameworks", searchTerms: "node nodejs javascript typescript express nestjs service", aliases: ["Node", "Express", "Express.js", "NestJS", "TypeScript", "JavaScript"] },
|
|
25334
|
+
{ type: "service", techStack: "Python", category: "Languages & Frameworks", searchTerms: "python service" },
|
|
25335
|
+
{ type: "service", techStack: "Django", category: "Languages & Frameworks", searchTerms: "django python service" },
|
|
25336
|
+
{ type: "service", techStack: "FastAPI", category: "Languages & Frameworks", searchTerms: "fastapi python service" },
|
|
25337
|
+
{ type: "service", techStack: "Flask", category: "Languages & Frameworks", searchTerms: "flask python service" },
|
|
25338
|
+
{ type: "service", techStack: "Ruby on Rails", category: "Languages & Frameworks", searchTerms: "ruby on rails service", aliases: ["Rails", "Ruby"] },
|
|
25339
|
+
{ type: "service", techStack: ".NET", category: "Languages & Frameworks", searchTerms: "dotnet .net asp.net c# service", aliases: ["ASP.NET", "ASP.NET Core", "dotnet", "C#"] },
|
|
25340
|
+
{ type: "service", techStack: "PHP", category: "Languages & Frameworks", searchTerms: "php laravel symfony service", aliases: ["Laravel", "Symfony"] },
|
|
25341
|
+
{ type: "service", techStack: "Rust", category: "Languages & Frameworks", searchTerms: "rust axum actix service", aliases: ["Axum", "Actix"] },
|
|
25342
|
+
{ type: "service", techStack: "Elixir", category: "Languages & Frameworks", searchTerms: "elixir phoenix erlang service", aliases: ["Phoenix", "Erlang"] },
|
|
25343
|
+
// Containers and platforms
|
|
25344
|
+
{ type: "container", techStack: "Kubernetes", category: "Containers & Platforms", searchTerms: "kubernetes k8s container pod", aliases: ["K8s", "Kubernetes Pod", "Pod"] },
|
|
25345
|
+
{ type: "container", techStack: "Docker", category: "Containers & Platforms", searchTerms: "docker container", aliases: ["Container", "Docker Container"] },
|
|
25346
|
+
{ type: "compute", techStack: "VM", category: "Containers & Platforms", searchTerms: "vm virtual machine server instance", aliases: ["Virtual Machine", "Instance", "Bare Metal"] },
|
|
25347
|
+
{ type: "serverless", techStack: "Cloudflare Workers", category: "Containers & Platforms", searchTerms: "cloudflare workers edge functions serverless", aliases: ["Workers"] },
|
|
25310
25348
|
// AWS Serverless
|
|
25311
|
-
{ type: "serverless", techStack: "AWS Lambda", category: "AWS Services", searchTerms: "aws lambda serverless function" },
|
|
25312
|
-
{ type: "serverless", techStack: "AWS API Gateway", category: "AWS Services", searchTerms: "aws api gateway rest" },
|
|
25349
|
+
{ type: "serverless", techStack: "AWS Lambda", category: "AWS Services", searchTerms: "aws lambda serverless function", aliases: ["Lambda", "Amazon Lambda"] },
|
|
25350
|
+
{ type: "serverless", techStack: "AWS API Gateway", category: "AWS Services", searchTerms: "aws api gateway rest", kind: "gateway", aliases: ["Amazon API Gateway"] },
|
|
25313
25351
|
// AWS Compute
|
|
25314
|
-
{ type: "compute", techStack: "AWS EC2", category: "AWS Services", searchTerms: "aws ec2 compute instance vm" },
|
|
25315
|
-
{ type: "container", techStack: "AWS ECS", category: "AWS Services", searchTerms: "aws ecs container docker" },
|
|
25316
|
-
{ type: "container", techStack: "AWS EKS", category: "AWS Services", searchTerms: "aws eks kubernetes k8s" },
|
|
25317
|
-
{ type: "container", techStack: "AWS Fargate", category: "AWS Services", searchTerms: "aws fargate serverless container" },
|
|
25352
|
+
{ type: "compute", techStack: "AWS EC2", category: "AWS Services", searchTerms: "aws ec2 compute instance vm", aliases: ["EC2", "Amazon EC2"] },
|
|
25353
|
+
{ type: "container", techStack: "AWS ECS", category: "AWS Services", searchTerms: "aws ecs container docker", aliases: ["ECS", "Amazon ECS"] },
|
|
25354
|
+
{ type: "container", techStack: "AWS EKS", category: "AWS Services", searchTerms: "aws eks kubernetes k8s", aliases: ["EKS", "Amazon EKS"] },
|
|
25355
|
+
{ type: "container", techStack: "AWS Fargate", category: "AWS Services", searchTerms: "aws fargate serverless container", aliases: ["Fargate"] },
|
|
25318
25356
|
// AWS Storage
|
|
25319
|
-
{ type: "storage", techStack: "AWS S3", category: "AWS Services", searchTerms: "aws s3 storage bucket object" },
|
|
25320
|
-
{ type: "storage", techStack: "AWS EBS", category: "AWS Services", searchTerms: "aws ebs storage volume disk" },
|
|
25321
|
-
{ type: "storage", techStack: "AWS EFS", category: "AWS Services", searchTerms: "aws efs storage file system" },
|
|
25357
|
+
{ type: "storage", techStack: "AWS S3", category: "AWS Services", searchTerms: "aws s3 storage bucket object", aliases: ["S3", "Amazon S3", "S3 Bucket"] },
|
|
25358
|
+
{ type: "storage", techStack: "AWS EBS", category: "AWS Services", searchTerms: "aws ebs storage volume disk", aliases: ["EBS", "Amazon EBS"] },
|
|
25359
|
+
{ type: "storage", techStack: "AWS EFS", category: "AWS Services", searchTerms: "aws efs storage file system", aliases: ["EFS", "Amazon EFS"] },
|
|
25322
25360
|
// AWS Database
|
|
25323
|
-
{ type: "database", techStack: "AWS RDS", category: "AWS Services", searchTerms: "aws rds database relational sql" },
|
|
25324
|
-
{ type: "database", techStack: "AWS DynamoDB", category: "AWS Services", searchTerms: "aws dynamodb database nosql" },
|
|
25325
|
-
{ type: "cache", techStack: "AWS ElastiCache", category: "AWS Services", searchTerms: "aws elasticache redis memcached cache" },
|
|
25326
|
-
{ type: "database", techStack: "AWS Aurora", category: "AWS Services", searchTerms: "aws aurora database mysql postgresql" },
|
|
25361
|
+
{ type: "database", techStack: "AWS RDS", category: "AWS Services", searchTerms: "aws rds database relational sql", aliases: ["RDS", "Amazon RDS"] },
|
|
25362
|
+
{ type: "database", techStack: "AWS DynamoDB", category: "AWS Services", searchTerms: "aws dynamodb database nosql", profile: "nosql", aliases: ["Amazon DynamoDB", "Dynamo"] },
|
|
25363
|
+
{ type: "cache", techStack: "AWS ElastiCache", category: "AWS Services", searchTerms: "aws elasticache redis memcached cache", aliases: ["ElastiCache", "Amazon ElastiCache"] },
|
|
25364
|
+
{ type: "database", techStack: "AWS Aurora", category: "AWS Services", searchTerms: "aws aurora database mysql postgresql", aliases: ["Aurora", "Amazon Aurora", "Aurora PostgreSQL", "Aurora MySQL", "Aurora Serverless"] },
|
|
25365
|
+
{ type: "database", techStack: "AWS DocumentDB", category: "AWS Services", searchTerms: "aws documentdb mongodb document database", aliases: ["DocumentDB", "Amazon DocumentDB"] },
|
|
25366
|
+
{ type: "database", techStack: "AWS Keyspaces", category: "AWS Services", searchTerms: "aws keyspaces cassandra nosql", profile: "nosql", aliases: ["Keyspaces", "Amazon Keyspaces"] },
|
|
25367
|
+
{ type: "database", techStack: "AWS Neptune", category: "AWS Services", searchTerms: "aws neptune graph database", aliases: ["Neptune", "Amazon Neptune"] },
|
|
25368
|
+
{ type: "database", techStack: "AWS Redshift", category: "AWS Services", searchTerms: "aws redshift data warehouse analytics", kind: "analytics", aliases: ["Redshift", "Amazon Redshift"] },
|
|
25369
|
+
{ type: "database", techStack: "AWS Athena", category: "AWS Services", searchTerms: "aws athena query s3 analytics", kind: "analytics", aliases: ["Athena", "Amazon Athena"] },
|
|
25370
|
+
{ type: "database", techStack: "AWS OpenSearch", category: "AWS Services", searchTerms: "aws opensearch elasticsearch search", kind: "search", aliases: ["Amazon OpenSearch", "OpenSearch Service", "AWS Elasticsearch"] },
|
|
25327
25371
|
// AWS Messaging
|
|
25328
|
-
{ type: "queue", techStack: "AWS SQS", category: "AWS Services", searchTerms: "aws sqs queue message" },
|
|
25329
|
-
{ type: "queue", techStack: "AWS SNS", category: "AWS Services", searchTerms: "aws sns notification topic pubsub" },
|
|
25330
|
-
{ type: "queue", techStack: "AWS EventBridge", category: "AWS Services", searchTerms: "aws eventbridge event bus" },
|
|
25331
|
-
{ type: "queue", techStack: "AWS Kinesis", category: "AWS Services", searchTerms: "aws kinesis stream data" },
|
|
25372
|
+
{ type: "queue", techStack: "AWS SQS", category: "AWS Services", searchTerms: "aws sqs queue message", aliases: ["Amazon SQS", "Simple Queue Service"] },
|
|
25373
|
+
{ type: "queue", techStack: "AWS SNS", category: "AWS Services", searchTerms: "aws sns notification topic pubsub", aliases: ["SNS", "Amazon SNS", "Simple Notification Service"] },
|
|
25374
|
+
{ type: "queue", techStack: "AWS EventBridge", category: "AWS Services", searchTerms: "aws eventbridge event bus", aliases: ["EventBridge", "Amazon EventBridge"] },
|
|
25375
|
+
{ type: "queue", techStack: "AWS Kinesis", category: "AWS Services", searchTerms: "aws kinesis stream data", aliases: ["Kinesis", "Amazon Kinesis", "Kinesis Data Streams"] },
|
|
25376
|
+
{ type: "queue", techStack: "AWS MSK", category: "AWS Services", searchTerms: "aws msk managed kafka stream", aliases: ["MSK", "Amazon MSK", "Managed Streaming for Kafka"] },
|
|
25332
25377
|
// AWS CDN/Network
|
|
25333
|
-
{ type: "cdn", techStack: "AWS CloudFront", category: "AWS Services", searchTerms: "aws cloudfront cdn edge" },
|
|
25334
|
-
{ type: "cdn", techStack: "AWS Route53", category: "AWS Services", searchTerms: "aws route53 dns domain" },
|
|
25335
|
-
{ type: "cdn", techStack: "AWS Load Balancer", category: "AWS Services", searchTerms: "aws alb elb load balancer" },
|
|
25378
|
+
{ type: "cdn", techStack: "AWS CloudFront", category: "AWS Services", searchTerms: "aws cloudfront cdn edge", kind: "cdn", aliases: ["CloudFront", "Amazon CloudFront"] },
|
|
25379
|
+
{ type: "cdn", techStack: "AWS Route53", category: "AWS Services", searchTerms: "aws route53 dns domain", kind: "dns", aliases: ["Route53", "Amazon Route 53"] },
|
|
25380
|
+
{ type: "cdn", techStack: "AWS Load Balancer", category: "AWS Services", searchTerms: "aws alb elb nlb load balancer", kind: "loadbalancer", aliases: ["ALB", "NLB", "ELB", "AWS ALB", "AWS NLB", "AWS ELB", "Application Load Balancer", "Network Load Balancer", "Elastic Load Balancer", "Elastic Load Balancing"] },
|
|
25381
|
+
{ type: "cdn", techStack: "AWS WAF", category: "AWS Services", searchTerms: "aws waf web application firewall", kind: "edge", aliases: ["Amazon WAF"] },
|
|
25382
|
+
{ type: "cdn", techStack: "AWS Global Accelerator", category: "AWS Services", searchTerms: "aws global accelerator anycast edge", kind: "edge", aliases: ["Global Accelerator"] },
|
|
25383
|
+
// AWS third-party style services
|
|
25384
|
+
{ type: "external", techStack: "AWS SES", category: "AWS Services", searchTerms: "aws ses email simple email service", aliases: ["SES", "Amazon SES", "Simple Email Service"] },
|
|
25385
|
+
{ type: "external", techStack: "AWS Cognito", category: "AWS Services", searchTerms: "aws cognito auth identity users", aliases: ["Cognito", "Amazon Cognito"] },
|
|
25336
25386
|
// GCP Serverless
|
|
25337
|
-
{ type: "serverless", techStack: "GCP Cloud Functions", category: "GCP Services", searchTerms: "gcp google cloud functions serverless" },
|
|
25338
|
-
{ type: "serverless", techStack: "GCP Cloud Run", category: "GCP Services", searchTerms: "gcp google cloud run serverless container" },
|
|
25339
|
-
{ type: "serverless", techStack: "GCP App Engine", category: "GCP Services", searchTerms: "gcp google app engine paas" },
|
|
25387
|
+
{ type: "serverless", techStack: "GCP Cloud Functions", category: "GCP Services", searchTerms: "gcp google cloud functions serverless", aliases: ["Cloud Functions", "Google Cloud Functions"] },
|
|
25388
|
+
{ type: "serverless", techStack: "GCP Cloud Run", category: "GCP Services", searchTerms: "gcp google cloud run serverless container", aliases: ["Cloud Run", "Google Cloud Run"] },
|
|
25389
|
+
{ type: "serverless", techStack: "GCP App Engine", category: "GCP Services", searchTerms: "gcp google app engine paas", aliases: ["App Engine", "Google App Engine"] },
|
|
25340
25390
|
// GCP Compute
|
|
25341
|
-
{ type: "compute", techStack: "GCP Compute Engine", category: "GCP Services", searchTerms: "gcp google compute engine vm instance" },
|
|
25342
|
-
{ type: "container", techStack: "GCP GKE", category: "GCP Services", searchTerms: "gcp google gke kubernetes k8s" },
|
|
25391
|
+
{ type: "compute", techStack: "GCP Compute Engine", category: "GCP Services", searchTerms: "gcp google compute engine vm instance", aliases: ["Compute Engine", "GCE", "Google Compute Engine"] },
|
|
25392
|
+
{ type: "container", techStack: "GCP GKE", category: "GCP Services", searchTerms: "gcp google gke kubernetes k8s", aliases: ["GKE", "Google Kubernetes Engine"] },
|
|
25343
25393
|
// GCP Storage
|
|
25344
|
-
{ type: "storage", techStack: "GCP Cloud Storage", category: "GCP Services", searchTerms: "gcp google cloud storage bucket object" },
|
|
25345
|
-
{ type: "storage", techStack: "GCP Persistent Disk", category: "GCP Services", searchTerms: "gcp google persistent disk storage" },
|
|
25394
|
+
{ type: "storage", techStack: "GCP Cloud Storage", category: "GCP Services", searchTerms: "gcp google cloud storage bucket object", aliases: ["GCS", "Cloud Storage", "Google Cloud Storage"] },
|
|
25395
|
+
{ type: "storage", techStack: "GCP Persistent Disk", category: "GCP Services", searchTerms: "gcp google persistent disk storage", aliases: ["Persistent Disk"] },
|
|
25346
25396
|
// GCP Database
|
|
25347
|
-
{ type: "database", techStack: "GCP Cloud SQL", category: "GCP Services", searchTerms: "gcp google cloud sql database" },
|
|
25348
|
-
{ type: "database", techStack: "GCP Firestore", category: "GCP Services", searchTerms: "gcp google firestore database nosql" },
|
|
25349
|
-
{ type: "database", techStack: "GCP Bigtable", category: "GCP Services", searchTerms: "gcp google bigtable database nosql" },
|
|
25350
|
-
{ type: "database", techStack: "GCP Spanner", category: "GCP Services", searchTerms: "gcp google spanner database sql" },
|
|
25351
|
-
{ type: "database", techStack: "GCP BigQuery", category: "GCP Services", searchTerms: "gcp google bigquery data warehouse analytics" },
|
|
25397
|
+
{ type: "database", techStack: "GCP Cloud SQL", category: "GCP Services", searchTerms: "gcp google cloud sql database", aliases: ["Cloud SQL", "Google Cloud SQL"] },
|
|
25398
|
+
{ type: "database", techStack: "GCP Firestore", category: "GCP Services", searchTerms: "gcp google firestore database nosql", profile: "nosqlStrong", aliases: ["Firestore", "Cloud Firestore", "Datastore"] },
|
|
25399
|
+
{ type: "database", techStack: "GCP Bigtable", category: "GCP Services", searchTerms: "gcp google bigtable database nosql", profile: "nosqlStrong", aliases: ["Bigtable", "Cloud Bigtable"] },
|
|
25400
|
+
{ type: "database", techStack: "GCP Spanner", category: "GCP Services", searchTerms: "gcp google spanner database sql", profile: "nosqlStrong", aliases: ["Spanner", "Cloud Spanner", "Google Spanner"] },
|
|
25401
|
+
{ type: "database", techStack: "GCP BigQuery", category: "GCP Services", searchTerms: "gcp google bigquery data warehouse analytics", kind: "analytics", aliases: ["BigQuery", "Google BigQuery"] },
|
|
25402
|
+
{ type: "cache", techStack: "GCP Memorystore", category: "GCP Services", searchTerms: "gcp google memorystore redis memcached cache", aliases: ["Memorystore"] },
|
|
25352
25403
|
// GCP Messaging
|
|
25353
|
-
{ type: "queue", techStack: "GCP Pub/Sub", category: "GCP Services", searchTerms: "gcp google pubsub pub/sub message queue" },
|
|
25404
|
+
{ type: "queue", techStack: "GCP Pub/Sub", category: "GCP Services", searchTerms: "gcp google pubsub pub/sub message queue", aliases: ["Pub/Sub", "Google Pub/Sub", "Cloud Pub/Sub"] },
|
|
25354
25405
|
// GCP CDN/Network
|
|
25355
|
-
{ type: "cdn", techStack: "GCP Cloud CDN", category: "GCP Services", searchTerms: "gcp google cloud cdn edge" },
|
|
25356
|
-
{ type: "cdn", techStack: "GCP Cloud DNS", category: "GCP Services", searchTerms: "gcp google cloud dns domain" },
|
|
25357
|
-
{ type: "cdn", techStack: "GCP Load Balancing", category: "GCP Services", searchTerms: "gcp google load balancing" },
|
|
25406
|
+
{ type: "cdn", techStack: "GCP Cloud CDN", category: "GCP Services", searchTerms: "gcp google cloud cdn edge", kind: "cdn", aliases: ["Cloud CDN", "Google Cloud CDN"] },
|
|
25407
|
+
{ type: "cdn", techStack: "GCP Cloud DNS", category: "GCP Services", searchTerms: "gcp google cloud dns domain", kind: "dns", aliases: ["Cloud DNS", "Google Cloud DNS"] },
|
|
25408
|
+
{ type: "cdn", techStack: "GCP Load Balancing", category: "GCP Services", searchTerms: "gcp google load balancing", kind: "loadbalancer", aliases: ["Cloud Load Balancing", "GCP Load Balancer", "Google Cloud Load Balancing"] },
|
|
25409
|
+
{ type: "cdn", techStack: "GCP Apigee", category: "GCP Services", searchTerms: "gcp google apigee api gateway management", kind: "gateway", aliases: ["Apigee"] },
|
|
25358
25410
|
// Azure Serverless
|
|
25359
|
-
{ type: "serverless", techStack: "Azure Functions", category: "Azure Services", searchTerms: "azure functions serverless" },
|
|
25360
|
-
{ type: "serverless", techStack: "Azure Logic Apps", category: "Azure Services", searchTerms: "azure logic apps workflow" },
|
|
25361
|
-
{ type: "serverless", techStack: "Azure API Management", category: "Azure Services", searchTerms: "azure api management gateway" },
|
|
25411
|
+
{ type: "serverless", techStack: "Azure Functions", category: "Azure Services", searchTerms: "azure functions serverless", aliases: ["Azure Function"] },
|
|
25412
|
+
{ type: "serverless", techStack: "Azure Logic Apps", category: "Azure Services", searchTerms: "azure logic apps workflow", aliases: ["Logic Apps"] },
|
|
25413
|
+
{ type: "serverless", techStack: "Azure API Management", category: "Azure Services", searchTerms: "azure api management gateway", kind: "gateway", aliases: ["APIM", "Azure APIM"] },
|
|
25362
25414
|
// Azure Compute
|
|
25363
|
-
{ type: "compute", techStack: "Azure VM", category: "Azure Services", searchTerms: "azure vm virtual machine compute" },
|
|
25364
|
-
{ type: "container", techStack: "Azure AKS", category: "Azure Services", searchTerms: "azure aks kubernetes k8s" },
|
|
25365
|
-
{ type: "container", techStack: "Azure Container Instances", category: "Azure Services", searchTerms: "azure container instances aci" },
|
|
25415
|
+
{ type: "compute", techStack: "Azure VM", category: "Azure Services", searchTerms: "azure vm virtual machine compute", aliases: ["Azure Virtual Machine", "Azure Virtual Machines"] },
|
|
25416
|
+
{ type: "container", techStack: "Azure AKS", category: "Azure Services", searchTerms: "azure aks kubernetes k8s", aliases: ["AKS", "Azure Kubernetes Service"] },
|
|
25417
|
+
{ type: "container", techStack: "Azure Container Instances", category: "Azure Services", searchTerms: "azure container instances aci", aliases: ["ACI"] },
|
|
25418
|
+
{ type: "service", techStack: "Azure App Service", category: "Azure Services", searchTerms: "azure app service web apps paas", aliases: ["App Service", "Azure Web Apps"] },
|
|
25366
25419
|
// Azure Storage
|
|
25367
|
-
{ type: "storage", techStack: "Azure Blob Storage", category: "Azure Services", searchTerms: "azure blob storage object" },
|
|
25420
|
+
{ type: "storage", techStack: "Azure Blob Storage", category: "Azure Services", searchTerms: "azure blob storage object", aliases: ["Azure Blob", "Azure Storage"] },
|
|
25368
25421
|
{ type: "storage", techStack: "Azure Files", category: "Azure Services", searchTerms: "azure files storage file system" },
|
|
25369
|
-
{ type: "storage", techStack: "Azure Disk Storage", category: "Azure Services", searchTerms: "azure disk storage volume" },
|
|
25422
|
+
{ type: "storage", techStack: "Azure Disk Storage", category: "Azure Services", searchTerms: "azure disk storage volume", aliases: ["Azure Managed Disks"] },
|
|
25370
25423
|
// Azure Database
|
|
25371
|
-
{ type: "database", techStack: "Azure SQL", category: "Azure Services", searchTerms: "azure sql database" },
|
|
25372
|
-
{ type: "database", techStack: "Azure Cosmos DB", category: "Azure Services", searchTerms: "azure cosmos db database nosql" },
|
|
25373
|
-
{ type: "database", techStack: "Azure Database for PostgreSQL", category: "Azure Services", searchTerms: "azure postgresql database" },
|
|
25374
|
-
{ type: "database", techStack: "Azure Database for MySQL", category: "Azure Services", searchTerms: "azure mysql database" },
|
|
25375
|
-
{ type: "cache", techStack: "Azure Cache for Redis", category: "Azure Services", searchTerms: "azure redis cache" },
|
|
25424
|
+
{ type: "database", techStack: "Azure SQL", category: "Azure Services", searchTerms: "azure sql database", aliases: ["Azure SQL Database"] },
|
|
25425
|
+
{ type: "database", techStack: "Azure Cosmos DB", category: "Azure Services", searchTerms: "azure cosmos db database nosql", profile: "nosql", aliases: ["Cosmos DB", "Cosmos"] },
|
|
25426
|
+
{ type: "database", techStack: "Azure Database for PostgreSQL", category: "Azure Services", searchTerms: "azure postgresql database", aliases: ["Azure PostgreSQL", "Azure Postgres"] },
|
|
25427
|
+
{ type: "database", techStack: "Azure Database for MySQL", category: "Azure Services", searchTerms: "azure mysql database", aliases: ["Azure MySQL"] },
|
|
25428
|
+
{ type: "cache", techStack: "Azure Cache for Redis", category: "Azure Services", searchTerms: "azure redis cache", aliases: ["Azure Redis"] },
|
|
25429
|
+
{ type: "database", techStack: "Azure AI Search", category: "Azure Services", searchTerms: "azure ai cognitive search", kind: "search", aliases: ["Azure Cognitive Search", "Azure Search"] },
|
|
25376
25430
|
// Azure Messaging
|
|
25377
|
-
{ type: "queue", techStack: "Azure Service Bus", category: "Azure Services", searchTerms: "azure service bus queue message" },
|
|
25378
|
-
{ type: "queue", techStack: "Azure Event Hubs", category: "Azure Services", searchTerms: "azure event hubs stream" },
|
|
25379
|
-
{ type: "queue", techStack: "Azure Event Grid", category: "Azure Services", searchTerms: "azure event grid" },
|
|
25380
|
-
{ type: "queue", techStack: "Azure Queue Storage", category: "Azure Services", searchTerms: "azure queue storage message" },
|
|
25431
|
+
{ type: "queue", techStack: "Azure Service Bus", category: "Azure Services", searchTerms: "azure service bus queue message", aliases: ["Service Bus"] },
|
|
25432
|
+
{ type: "queue", techStack: "Azure Event Hubs", category: "Azure Services", searchTerms: "azure event hubs stream", aliases: ["Event Hubs", "Event Hub"] },
|
|
25433
|
+
{ type: "queue", techStack: "Azure Event Grid", category: "Azure Services", searchTerms: "azure event grid", aliases: ["Event Grid"] },
|
|
25434
|
+
{ type: "queue", techStack: "Azure Queue Storage", category: "Azure Services", searchTerms: "azure queue storage message", aliases: ["Azure Queue", "Azure Queues"] },
|
|
25381
25435
|
// Azure CDN/Network
|
|
25382
|
-
{ type: "cdn", techStack: "Azure CDN", category: "Azure Services", searchTerms: "azure cdn edge" },
|
|
25383
|
-
{ type: "cdn", techStack: "Azure DNS", category: "Azure Services", searchTerms: "azure dns domain" },
|
|
25384
|
-
{ type: "cdn", techStack: "Azure Front Door", category: "Azure Services", searchTerms: "azure front door cdn" },
|
|
25436
|
+
{ type: "cdn", techStack: "Azure CDN", category: "Azure Services", searchTerms: "azure cdn edge", kind: "cdn" },
|
|
25437
|
+
{ type: "cdn", techStack: "Azure DNS", category: "Azure Services", searchTerms: "azure dns domain", kind: "dns" },
|
|
25438
|
+
{ type: "cdn", techStack: "Azure Front Door", category: "Azure Services", searchTerms: "azure front door cdn", kind: "cdn", aliases: ["Front Door"] },
|
|
25439
|
+
{ type: "cdn", techStack: "Azure Load Balancer", category: "Azure Services", searchTerms: "azure load balancer", kind: "loadbalancer" },
|
|
25440
|
+
{ type: "cdn", techStack: "Azure Application Gateway", category: "Azure Services", searchTerms: "azure application gateway l7 load balancer", kind: "loadbalancer", aliases: ["Application Gateway", "Azure App Gateway"] },
|
|
25385
25441
|
// Traditional Databases
|
|
25386
|
-
{ type: "database", techStack: "PostgreSQL", category: "Databases", searchTerms: "postgresql postgres sql database" },
|
|
25387
|
-
{ type: "database", techStack: "MySQL", category: "Databases", searchTerms: "mysql sql database" },
|
|
25388
|
-
{ type: "database", techStack: "MongoDB", category: "Databases", searchTerms: "mongodb mongo nosql database" },
|
|
25389
|
-
{ type: "database", techStack: "Redis", category: "Databases", searchTerms: "redis cache database key-value" },
|
|
25390
|
-
{ type: "database", techStack: "DynamoDB", category: "Databases", searchTerms: "dynamodb nosql database aws" },
|
|
25391
|
-
{ type: "database", techStack: "Cassandra", category: "Databases", searchTerms: "cassandra nosql database" },
|
|
25392
|
-
{ type: "database", techStack: "CouchDB", category: "Databases", searchTerms: "couchdb nosql database" },
|
|
25393
|
-
{ type: "database", techStack: "Elasticsearch", category: "Databases", searchTerms: "elasticsearch search database" },
|
|
25394
|
-
{ type: "database", techStack: "Neo4j", category: "Databases", searchTerms: "neo4j graph database" },
|
|
25395
|
-
{ type: "database", techStack: "InfluxDB", category: "Databases", searchTerms: "influxdb timeseries database" },
|
|
25396
|
-
{ type: "database", techStack: "TimescaleDB", category: "Databases", searchTerms: "timescaledb timeseries postgresql database" },
|
|
25397
|
-
{ type: "database", techStack: "MariaDB", category: "Databases", searchTerms: "mariadb mysql sql database" },
|
|
25442
|
+
{ type: "database", techStack: "PostgreSQL", category: "Databases", searchTerms: "postgresql postgres sql database", aliases: ["Postgres", "PG", "PostGIS", "pgvector"] },
|
|
25443
|
+
{ type: "database", techStack: "MySQL", category: "Databases", searchTerms: "mysql sql database", aliases: ["Percona"] },
|
|
25444
|
+
{ type: "database", techStack: "MongoDB", category: "Databases", searchTerms: "mongodb mongo nosql database", profile: "nosqlStrong", aliases: ["Mongo", "MongoDB Atlas"] },
|
|
25445
|
+
{ type: "database", techStack: "Redis", category: "Databases", searchTerms: "redis cache database key-value", kind: "cache", aliases: ["Redis Cluster", "Redis Cache"] },
|
|
25446
|
+
{ type: "database", techStack: "DynamoDB", category: "Databases", searchTerms: "dynamodb nosql database aws", profile: "nosql" },
|
|
25447
|
+
{ type: "database", techStack: "Cassandra", category: "Databases", searchTerms: "cassandra nosql database", profile: "nosql", aliases: ["Apache Cassandra", "DataStax"] },
|
|
25448
|
+
{ type: "database", techStack: "CouchDB", category: "Databases", searchTerms: "couchdb nosql database", profile: "nosql", aliases: ["Apache CouchDB"] },
|
|
25449
|
+
{ type: "database", techStack: "Elasticsearch", category: "Databases", searchTerms: "elasticsearch search database", kind: "search", aliases: ["Elastic", "Elastic Search", "ES"] },
|
|
25450
|
+
{ type: "database", techStack: "Neo4j", category: "Databases", searchTerms: "neo4j graph database", aliases: ["Graph Database", "Graph DB"] },
|
|
25451
|
+
{ type: "database", techStack: "InfluxDB", category: "Databases", searchTerms: "influxdb timeseries database", kind: "analytics", aliases: ["Influx"] },
|
|
25452
|
+
{ type: "database", techStack: "TimescaleDB", category: "Databases", searchTerms: "timescaledb timeseries postgresql database", kind: "analytics", aliases: ["Timescale"] },
|
|
25453
|
+
{ type: "database", techStack: "MariaDB", category: "Databases", searchTerms: "mariadb mysql sql database", aliases: ["Maria"] },
|
|
25398
25454
|
{ type: "database", techStack: "SQLite", category: "Databases", searchTerms: "sqlite sql database" },
|
|
25399
|
-
{ type: "database", techStack: "Oracle", category: "Databases", searchTerms: "oracle sql database" },
|
|
25400
|
-
{ type: "database", techStack: "SQL Server", category: "Databases", searchTerms: "sql server mssql database microsoft" },
|
|
25455
|
+
{ type: "database", techStack: "Oracle", category: "Databases", searchTerms: "oracle sql database", aliases: ["Oracle DB", "Oracle Database"] },
|
|
25456
|
+
{ type: "database", techStack: "SQL Server", category: "Databases", searchTerms: "sql server mssql database microsoft", aliases: ["MSSQL", "MS SQL", "Microsoft SQL Server"] },
|
|
25457
|
+
{ type: "database", techStack: "CockroachDB", category: "Databases", searchTerms: "cockroachdb distributed sql database", profile: "nosqlStrong", aliases: ["Cockroach"] },
|
|
25458
|
+
{ type: "database", techStack: "YugabyteDB", category: "Databases", searchTerms: "yugabytedb distributed sql database", profile: "nosqlStrong", aliases: ["Yugabyte"] },
|
|
25459
|
+
{ type: "database", techStack: "TiDB", category: "Databases", searchTerms: "tidb distributed sql mysql database", profile: "nosqlStrong" },
|
|
25460
|
+
{ type: "database", techStack: "Vitess", category: "Databases", searchTerms: "vitess sharded mysql planetscale database", profile: "nosqlStrong", aliases: ["PlanetScale"] },
|
|
25461
|
+
{ type: "database", techStack: "ScyllaDB", category: "Databases", searchTerms: "scylladb cassandra nosql database", profile: "nosql", aliases: ["Scylla"] },
|
|
25462
|
+
{ type: "database", techStack: "Couchbase", category: "Databases", searchTerms: "couchbase nosql document database", profile: "nosql" },
|
|
25463
|
+
{ type: "database", techStack: "HBase", category: "Databases", searchTerms: "hbase hadoop wide column nosql database", profile: "nosqlStrong", aliases: ["Apache HBase"] },
|
|
25464
|
+
{ type: "database", techStack: "etcd", category: "Databases", searchTerms: "etcd key value consensus configuration" },
|
|
25465
|
+
{ type: "database", techStack: "ZooKeeper", category: "Databases", searchTerms: "zookeeper coordination consensus configuration", aliases: ["Apache ZooKeeper"] },
|
|
25401
25466
|
// Cache & In-Memory
|
|
25402
|
-
{ type: "cache", techStack: "Memcached", category: "Cache & In-Memory", searchTerms: "memcached cache memory" },
|
|
25467
|
+
{ type: "cache", techStack: "Memcached", category: "Cache & In-Memory", searchTerms: "memcached cache memory", aliases: ["Memcache"] },
|
|
25403
25468
|
{ type: "cache", techStack: "Hazelcast", category: "Cache & In-Memory", searchTerms: "hazelcast cache memory" },
|
|
25404
25469
|
{ type: "cache", techStack: "Aerospike", category: "Cache & In-Memory", searchTerms: "aerospike cache database" },
|
|
25470
|
+
{ type: "cache", techStack: "Valkey", category: "Cache & In-Memory", searchTerms: "valkey redis cache memory" },
|
|
25471
|
+
{ type: "cache", techStack: "KeyDB", category: "Cache & In-Memory", searchTerms: "keydb redis cache memory" },
|
|
25472
|
+
{ type: "cache", techStack: "Dragonfly", category: "Cache & In-Memory", searchTerms: "dragonfly dragonflydb redis cache memory", aliases: ["DragonflyDB"] },
|
|
25405
25473
|
// Message Queues
|
|
25406
|
-
{ type: "queue", techStack: "Kafka", category: "Message Queues", searchTerms: "kafka message queue stream" },
|
|
25407
|
-
{ type: "queue", techStack: "RabbitMQ", category: "Message Queues", searchTerms: "rabbitmq message queue amqp" },
|
|
25474
|
+
{ type: "queue", techStack: "Kafka", category: "Message Queues", searchTerms: "kafka message queue stream", aliases: ["Apache Kafka", "Confluent", "Confluent Kafka", "Kafka Topic"] },
|
|
25475
|
+
{ type: "queue", techStack: "RabbitMQ", category: "Message Queues", searchTerms: "rabbitmq message queue amqp", aliases: ["Rabbit", "AMQP"] },
|
|
25408
25476
|
{ type: "queue", techStack: "SQS", category: "Message Queues", searchTerms: "sqs queue aws message" },
|
|
25409
|
-
{ type: "queue", techStack: "Redis Queue", category: "Message Queues", searchTerms: "redis queue message" },
|
|
25410
|
-
{ type: "queue", techStack: "NATS", category: "Message Queues", searchTerms: "nats message queue" },
|
|
25411
|
-
{ type: "queue", techStack: "Apache Pulsar", category: "Message Queues", searchTerms: "pulsar message queue stream" },
|
|
25412
|
-
{ type: "queue", techStack: "ActiveMQ", category: "Message Queues", searchTerms: "activemq message queue" },
|
|
25413
|
-
{ type: "queue", techStack: "ZeroMQ", category: "Message Queues", searchTerms: "zeromq zmq message queue" },
|
|
25477
|
+
{ type: "queue", techStack: "Redis Queue", category: "Message Queues", searchTerms: "redis queue message", aliases: ["Redis Streams", "Redis Pub/Sub"] },
|
|
25478
|
+
{ type: "queue", techStack: "NATS", category: "Message Queues", searchTerms: "nats message queue", aliases: ["NATS JetStream", "JetStream"] },
|
|
25479
|
+
{ type: "queue", techStack: "Apache Pulsar", category: "Message Queues", searchTerms: "pulsar message queue stream", aliases: ["Pulsar"] },
|
|
25480
|
+
{ type: "queue", techStack: "ActiveMQ", category: "Message Queues", searchTerms: "activemq message queue", aliases: ["Apache ActiveMQ", "Amazon MQ"] },
|
|
25481
|
+
{ type: "queue", techStack: "ZeroMQ", category: "Message Queues", searchTerms: "zeromq zmq message queue", aliases: ["ZMQ", "0MQ"] },
|
|
25482
|
+
{ type: "queue", techStack: "Redpanda", category: "Message Queues", searchTerms: "redpanda kafka stream queue" },
|
|
25483
|
+
// Search
|
|
25484
|
+
{ type: "database", techStack: "OpenSearch", category: "Search", searchTerms: "opensearch elasticsearch search", kind: "search" },
|
|
25485
|
+
{ type: "database", techStack: "Solr", category: "Search", searchTerms: "solr lucene search", kind: "search", aliases: ["Apache Solr"] },
|
|
25486
|
+
{ type: "database", techStack: "Meilisearch", category: "Search", searchTerms: "meilisearch search", kind: "search" },
|
|
25487
|
+
{ type: "database", techStack: "Typesense", category: "Search", searchTerms: "typesense search", kind: "search" },
|
|
25488
|
+
{ type: "database", techStack: "Algolia", category: "Search", searchTerms: "algolia hosted search", kind: "search" },
|
|
25489
|
+
// Analytics
|
|
25490
|
+
{ type: "database", techStack: "Snowflake", category: "Analytics", searchTerms: "snowflake data warehouse analytics", kind: "analytics" },
|
|
25491
|
+
{ type: "database", techStack: "ClickHouse", category: "Analytics", searchTerms: "clickhouse olap analytics columnar", kind: "analytics" },
|
|
25492
|
+
{ type: "database", techStack: "Apache Druid", category: "Analytics", searchTerms: "druid olap analytics", kind: "analytics", aliases: ["Druid"] },
|
|
25493
|
+
{ type: "database", techStack: "Apache Pinot", category: "Analytics", searchTerms: "pinot olap analytics", kind: "analytics", aliases: ["Pinot"] },
|
|
25494
|
+
{ type: "database", techStack: "Databricks", category: "Analytics", searchTerms: "databricks lakehouse spark analytics", kind: "analytics" },
|
|
25495
|
+
{ type: "database", techStack: "Prometheus", category: "Analytics", searchTerms: "prometheus metrics time series monitoring", kind: "analytics" },
|
|
25496
|
+
// Storage
|
|
25497
|
+
{ type: "storage", techStack: "MinIO", category: "Storage", searchTerms: "minio s3 object storage" },
|
|
25498
|
+
{ type: "storage", techStack: "Cloudflare R2", category: "Storage", searchTerms: "cloudflare r2 object storage", aliases: ["R2"] },
|
|
25499
|
+
{ type: "storage", techStack: "Ceph", category: "Storage", searchTerms: "ceph object block storage" },
|
|
25500
|
+
{ type: "storage", techStack: "HDFS", category: "Storage", searchTerms: "hdfs hadoop file system storage", aliases: ["Hadoop", "Hadoop HDFS"] },
|
|
25501
|
+
// CDN, proxies and gateways
|
|
25502
|
+
{ type: "cdn", techStack: "Cloudflare", category: "CDN & Edge", searchTerms: "cloudflare cdn edge", kind: "cdn", aliases: ["Cloudflare CDN"] },
|
|
25503
|
+
{ type: "cdn", techStack: "Fastly", category: "CDN & Edge", searchTerms: "fastly cdn edge", kind: "cdn" },
|
|
25504
|
+
{ type: "cdn", techStack: "Akamai", category: "CDN & Edge", searchTerms: "akamai cdn edge", kind: "cdn" },
|
|
25505
|
+
{ type: "cdn", techStack: "nginx", category: "CDN & Edge", searchTerms: "nginx reverse proxy load balancer web server", kind: "loadbalancer", aliases: ["NGINX Plus", "OpenResty"] },
|
|
25506
|
+
{ type: "cdn", techStack: "Envoy", category: "CDN & Edge", searchTerms: "envoy proxy load balancer service mesh", kind: "loadbalancer", aliases: ["Envoy Proxy"] },
|
|
25507
|
+
{ type: "cdn", techStack: "HAProxy", category: "CDN & Edge", searchTerms: "haproxy load balancer proxy", kind: "loadbalancer" },
|
|
25508
|
+
{ type: "cdn", techStack: "Traefik", category: "CDN & Edge", searchTerms: "traefik reverse proxy load balancer", kind: "loadbalancer" },
|
|
25509
|
+
{ type: "cdn", techStack: "Kubernetes Ingress", category: "CDN & Edge", searchTerms: "kubernetes ingress controller load balancer", kind: "loadbalancer", aliases: ["Ingress", "Ingress Controller", "K8s Ingress"] },
|
|
25510
|
+
{ type: "cdn", techStack: "Kong", category: "CDN & Edge", searchTerms: "kong api gateway", kind: "gateway", aliases: ["Kong Gateway"] },
|
|
25511
|
+
{ type: "cdn", techStack: "Spring Cloud Gateway", category: "CDN & Edge", searchTerms: "spring cloud gateway api gateway", kind: "gateway" },
|
|
25414
25512
|
// External Systems
|
|
25415
|
-
{ type: "external", techStack: "Third Party API", category: "External Systems", searchTerms: "third party api external" },
|
|
25416
|
-
{ type: "external", techStack: "Payment Gateway", category: "External Systems", searchTerms: "payment gateway stripe paypal" },
|
|
25417
|
-
{ type: "external", techStack: "Auth Service", category: "External Systems", searchTerms: "auth authentication authorization" },
|
|
25418
|
-
{ type: "external", techStack: "Email Service", category: "External Systems", searchTerms: "email service sendgrid mailgun" },
|
|
25419
|
-
{ type: "external", techStack: "SMS Service", category: "External Systems", searchTerms: "sms service twilio" },
|
|
25420
|
-
{ type: "external", techStack: "
|
|
25513
|
+
{ type: "external", techStack: "Third Party API", category: "External Systems", searchTerms: "third party api external", aliases: ["External API", "External Service", "Third-Party API", "3rd Party API", "Partner API", "SaaS", "Webhook"] },
|
|
25514
|
+
{ type: "external", techStack: "Payment Gateway", category: "External Systems", searchTerms: "payment gateway stripe paypal", aliases: ["Payment Provider", "Payments API", "Payment Processor", "PSP"] },
|
|
25515
|
+
{ type: "external", techStack: "Auth Service", category: "External Systems", searchTerms: "auth authentication authorization", aliases: ["Identity Provider", "IdP", "OAuth Provider", "SSO"] },
|
|
25516
|
+
{ type: "external", techStack: "Email Service", category: "External Systems", searchTerms: "email service sendgrid mailgun", aliases: ["Email", "Email Provider", "SMTP"] },
|
|
25517
|
+
{ type: "external", techStack: "SMS Service", category: "External Systems", searchTerms: "sms service twilio", aliases: ["SMS", "SMS Gateway", "SMS Provider"] },
|
|
25518
|
+
{ type: "external", techStack: "Push Service", category: "External Systems", searchTerms: "push notification service mobile", aliases: ["Push Notifications", "Push Notification Service", "Push Provider"] },
|
|
25519
|
+
{ type: "external", techStack: "Analytics Service", category: "External Systems", searchTerms: "analytics service google analytics", aliases: ["Google Analytics", "Segment", "Mixpanel", "Amplitude"] },
|
|
25520
|
+
{ type: "external", techStack: "Stripe", category: "External Systems", searchTerms: "stripe payments" },
|
|
25521
|
+
{ type: "external", techStack: "PayPal", category: "External Systems", searchTerms: "paypal payments" },
|
|
25522
|
+
{ type: "external", techStack: "Adyen", category: "External Systems", searchTerms: "adyen payments" },
|
|
25523
|
+
{ type: "external", techStack: "Braintree", category: "External Systems", searchTerms: "braintree payments" },
|
|
25524
|
+
{ type: "external", techStack: "Twilio", category: "External Systems", searchTerms: "twilio sms voice" },
|
|
25525
|
+
{ type: "external", techStack: "SendGrid", category: "External Systems", searchTerms: "sendgrid email" },
|
|
25526
|
+
{ type: "external", techStack: "Mailgun", category: "External Systems", searchTerms: "mailgun email" },
|
|
25527
|
+
{ type: "external", techStack: "Auth0", category: "External Systems", searchTerms: "auth0 identity login" },
|
|
25528
|
+
{ type: "external", techStack: "Okta", category: "External Systems", searchTerms: "okta identity sso" },
|
|
25529
|
+
{ type: "external", techStack: "Google Maps", category: "External Systems", searchTerms: "google maps geocoding routing", aliases: ["Google Maps API", "Maps API"] },
|
|
25530
|
+
{ type: "external", techStack: "APNs", category: "External Systems", searchTerms: "apns apple push notification", aliases: ["Apple Push Notification Service", "Apple Push"] },
|
|
25531
|
+
{ type: "external", techStack: "FCM", category: "External Systems", searchTerms: "fcm firebase cloud messaging push", aliases: ["Firebase Cloud Messaging", "Firebase Messaging"] },
|
|
25532
|
+
{ type: "external", techStack: "OpenAI", category: "External Systems", searchTerms: "openai llm ai api", aliases: ["OpenAI API", "LLM API", "LLM"] },
|
|
25421
25533
|
// Text & Annotations
|
|
25422
25534
|
{ type: "text", techStack: "Text Note", category: "Annotations", searchTerms: "text note annotation comment label" },
|
|
25423
25535
|
{ type: "text", techStack: "Sticky Note", category: "Annotations", searchTerms: "sticky note annotation comment" },
|
|
@@ -25428,6 +25540,54 @@ var componentCatalog = [
|
|
|
25428
25540
|
{ type: "group", techStack: "Security Zone", category: "Grouping", searchTerms: "group security zone boundary" },
|
|
25429
25541
|
{ type: "group", techStack: "Service Group", category: "Grouping", searchTerms: "group service container boundary" }
|
|
25430
25542
|
];
|
|
25543
|
+
var techKey = (name) => name.toLowerCase().replace(/[^a-z0-9#+]/g, "");
|
|
25544
|
+
var entries = componentCatalog;
|
|
25545
|
+
var byKey = /* @__PURE__ */ new Map();
|
|
25546
|
+
for (const c of entries) {
|
|
25547
|
+
for (const name of [c.techStack, ...c.aliases ?? []]) {
|
|
25548
|
+
const key = techKey(name);
|
|
25549
|
+
if (!byKey.has(key)) byKey.set(key, c);
|
|
25550
|
+
}
|
|
25551
|
+
}
|
|
25552
|
+
var VERSION = /[\s-]*v?\d+(\.(\d+|x))*\s*$/i;
|
|
25553
|
+
function findTech(name) {
|
|
25554
|
+
const exact = byKey.get(techKey(name));
|
|
25555
|
+
if (exact) return exact;
|
|
25556
|
+
const unversioned = name.replace(VERSION, "");
|
|
25557
|
+
return unversioned !== name && unversioned.trim() ? byKey.get(techKey(unversioned)) : void 0;
|
|
25558
|
+
}
|
|
25559
|
+
function editDistance(a, b) {
|
|
25560
|
+
const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
25561
|
+
for (let i = 1; i <= a.length; i++) {
|
|
25562
|
+
for (let j = 1; j <= b.length; j++) {
|
|
25563
|
+
const cost2 = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
25564
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost2);
|
|
25565
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
25566
|
+
}
|
|
25567
|
+
}
|
|
25568
|
+
return d[a.length][b.length];
|
|
25569
|
+
}
|
|
25570
|
+
function suggestTech(name, accept = (c) => c.type !== "group" && c.type !== "text" && c.techStack !== "Note") {
|
|
25571
|
+
const key = techKey(name);
|
|
25572
|
+
if (!key) return void 0;
|
|
25573
|
+
let best;
|
|
25574
|
+
for (const c of entries) {
|
|
25575
|
+
if (!accept(c)) continue;
|
|
25576
|
+
for (const candidate of [c.techStack, ...c.aliases ?? []]) {
|
|
25577
|
+
const d = editDistance(key, techKey(candidate));
|
|
25578
|
+
if (!best || d < best.d) best = { tech: c.techStack, d };
|
|
25579
|
+
}
|
|
25580
|
+
}
|
|
25581
|
+
if (best && best.d <= Math.max(1, Math.floor(key.length / 4))) return best.tech;
|
|
25582
|
+
const words = name.toLowerCase().split(/[^a-z0-9.#+]+/).filter(Boolean);
|
|
25583
|
+
for (let n = Math.min(3, words.length); n >= 1; n--) {
|
|
25584
|
+
for (let i = 0; i + n <= words.length; i++) {
|
|
25585
|
+
const found = byKey.get(techKey(words.slice(i, i + n).join(" ")));
|
|
25586
|
+
if (found && accept(found) && techKey(words.slice(i, i + n).join("")).length >= 3) return found.techStack;
|
|
25587
|
+
}
|
|
25588
|
+
}
|
|
25589
|
+
return void 0;
|
|
25590
|
+
}
|
|
25431
25591
|
|
|
25432
25592
|
// ../frontend/src/dsl/lexer.ts
|
|
25433
25593
|
var IDENT_START = /[A-Za-z_]/;
|
|
@@ -25638,26 +25798,7 @@ function endpointGroupKey(endpoint, all) {
|
|
|
25638
25798
|
var KINDS = ["client", "edge", "cdn", "loadbalancer", "gateway", "dns", "service", "function", "cache", "database", "search", "analytics", "queue", "storage", "external", "other"];
|
|
25639
25799
|
var DATA_STORE_KINDS = ["cache", "database", "search", "analytics", "queue", "storage"];
|
|
25640
25800
|
var EDGE_KINDS = ["edge", "cdn", "loadbalancer", "gateway", "dns"];
|
|
25641
|
-
var BY_TECH =
|
|
25642
|
-
"AWS CloudFront": "cdn",
|
|
25643
|
-
"Azure CDN": "cdn",
|
|
25644
|
-
"GCP Cloud CDN": "cdn",
|
|
25645
|
-
"Azure Front Door": "cdn",
|
|
25646
|
-
"AWS Load Balancer": "loadbalancer",
|
|
25647
|
-
"GCP Load Balancing": "loadbalancer",
|
|
25648
|
-
"AWS API Gateway": "gateway",
|
|
25649
|
-
"Azure API Management": "gateway",
|
|
25650
|
-
"AWS Route53": "dns",
|
|
25651
|
-
"Azure DNS": "dns",
|
|
25652
|
-
"GCP Cloud DNS": "dns",
|
|
25653
|
-
Redis: "cache",
|
|
25654
|
-
Elasticsearch: "search",
|
|
25655
|
-
"GCP BigQuery": "analytics",
|
|
25656
|
-
InfluxDB: "analytics",
|
|
25657
|
-
TimescaleDB: "analytics",
|
|
25658
|
-
// A note shape annotates the diagram; it is not a client.
|
|
25659
|
-
Note: "other"
|
|
25660
|
-
};
|
|
25801
|
+
var BY_TECH = new Map(componentCatalog.flatMap((c) => "kind" in c ? [[c.techStack, c.kind]] : []));
|
|
25661
25802
|
var BY_TYPE = {
|
|
25662
25803
|
shape: "client",
|
|
25663
25804
|
cdn: "edge",
|
|
@@ -25673,7 +25814,44 @@ var BY_TYPE = {
|
|
|
25673
25814
|
};
|
|
25674
25815
|
function kindOf(node) {
|
|
25675
25816
|
if (node.kind !== "component") return "other";
|
|
25676
|
-
return BY_TECH
|
|
25817
|
+
return node.inferredKind ?? BY_TECH.get(node.techStack) ?? BY_TYPE[node.type] ?? "other";
|
|
25818
|
+
}
|
|
25819
|
+
var TYPE_OF_KIND = {
|
|
25820
|
+
client: "shape",
|
|
25821
|
+
edge: "cdn",
|
|
25822
|
+
cdn: "cdn",
|
|
25823
|
+
loadbalancer: "cdn",
|
|
25824
|
+
gateway: "cdn",
|
|
25825
|
+
dns: "cdn",
|
|
25826
|
+
service: "service",
|
|
25827
|
+
function: "serverless",
|
|
25828
|
+
cache: "cache",
|
|
25829
|
+
database: "database",
|
|
25830
|
+
search: "database",
|
|
25831
|
+
analytics: "database",
|
|
25832
|
+
queue: "queue",
|
|
25833
|
+
storage: "storage",
|
|
25834
|
+
external: "external",
|
|
25835
|
+
other: "shape"
|
|
25836
|
+
};
|
|
25837
|
+
var KIND_WORDS = [
|
|
25838
|
+
[/\bsms\b|e-?mail|third.?party|3rd.?party|external|saas|webhook/, "external"],
|
|
25839
|
+
[/\bcdn\b|edge.?cache|content.?delivery/, "cdn"],
|
|
25840
|
+
[/\bdns\b/, "dns"],
|
|
25841
|
+
[/load.?balanc|\blb\b|\balb\b|\bnlb\b|proxy|ingress/, "loadbalancer"],
|
|
25842
|
+
[/gateway|\bapim?\b.*manage/, "gateway"],
|
|
25843
|
+
[/\bwaf\b|firewall/, "edge"],
|
|
25844
|
+
[/cache|memcache/, "cache"],
|
|
25845
|
+
[/queue|\bmq\b|mq$|broker|event.?stream|\bbus\b|topic|pub.?sub|event.?hub|kafka/, "queue"],
|
|
25846
|
+
[/search|lucene|\bindex\b/, "search"],
|
|
25847
|
+
[/warehouse|analytic|olap|data.?lake|lakehouse|time.?series|\btsdb\b/, "analytics"],
|
|
25848
|
+
[/bucket|blob|object.?stor|storage|\bfs\b|file.?system|\bnas\b|\bdisk\b|volume/, "storage"],
|
|
25849
|
+
[/\bdb\b|db$|database|\bsql\b|sql$|nosql|\bstore\b|ledger|key.?value|\bkv\b/, "database"],
|
|
25850
|
+
[/lambda|function|serverless|\bfaas\b/, "function"]
|
|
25851
|
+
];
|
|
25852
|
+
function kindFromName(tech) {
|
|
25853
|
+
const text = tech.toLowerCase();
|
|
25854
|
+
return KIND_WORDS.find(([words]) => words.test(text))?.[1];
|
|
25677
25855
|
}
|
|
25678
25856
|
function isKind(word) {
|
|
25679
25857
|
return KINDS.includes(word);
|
|
@@ -25795,12 +25973,32 @@ var UNIT_EXAMPLES = {
|
|
|
25795
25973
|
MBps: "a bandwidth, e.g. 500 MB/s or 1 GB/s",
|
|
25796
25974
|
"usd/GB": "a price per gigabyte, e.g. 0.05 usd/GB"
|
|
25797
25975
|
};
|
|
25798
|
-
var CAPACITY_PARTS = "a rate, reads, writes, shards, latency, availability, cost, durable or volatile, consistency, bandwidth or
|
|
25976
|
+
var CAPACITY_PARTS = "a rate, reads, writes, shards, latency, availability, cost, durable or volatile, consistency, bandwidth, egress or timeout";
|
|
25799
25977
|
var ASSERTION_HELP = 'Expected an assertion: "Use case" calls <node>, "Use case" writes <node> before responding, <node> calls <node>, no path from <node> to <node>, <node> has replicas >= 2, \u2026';
|
|
25800
25978
|
var ASSERTION_VERBS = "calls, every scenario calls, never calls, never waits for, writes, responds, starts at, has scenario or handles failure of";
|
|
25801
|
-
var
|
|
25802
|
-
|
|
25803
|
-
|
|
25979
|
+
var GENERIC_TECH = {
|
|
25980
|
+
client: "Actor",
|
|
25981
|
+
edge: "WAF",
|
|
25982
|
+
cdn: "CDN",
|
|
25983
|
+
loadbalancer: "Load Balancer",
|
|
25984
|
+
gateway: "API Gateway",
|
|
25985
|
+
dns: "DNS",
|
|
25986
|
+
service: "Service",
|
|
25987
|
+
function: "Function",
|
|
25988
|
+
cache: "Cache",
|
|
25989
|
+
database: "Database",
|
|
25990
|
+
search: "Search Engine",
|
|
25991
|
+
analytics: "Data Warehouse",
|
|
25992
|
+
queue: "Message Queue",
|
|
25993
|
+
storage: "Object Storage",
|
|
25994
|
+
external: "Third Party API",
|
|
25995
|
+
other: "Note"
|
|
25996
|
+
};
|
|
25997
|
+
function unknownTechMessage(tech, kind, suggestion) {
|
|
25998
|
+
const simulated = `simulated as a generic ${kind}${suggestion === GENERIC_TECH[kind] ? "" : `, like [${GENERIC_TECH[kind]}]`}`;
|
|
25999
|
+
return suggestion ? `Unknown tech stack '${tech}'. Did you mean '${suggestion}'? Until then it is ${simulated}` : `Unknown tech stack '${tech}'; ${simulated}`;
|
|
26000
|
+
}
|
|
26001
|
+
var DID_YOU_MEAN = /^Unknown tech stack '(.*)'\. Did you mean '([^']+)'\?/;
|
|
25804
26002
|
function parse(source2, options = {}) {
|
|
25805
26003
|
return new Parser(source2, options).run();
|
|
25806
26004
|
}
|
|
@@ -26052,8 +26250,8 @@ var Parser = class {
|
|
|
26052
26250
|
if (tokens[i]?.kind === "string") name = tokens[i++].value;
|
|
26053
26251
|
if (tokens[i]?.kind === "tech") {
|
|
26054
26252
|
const tech = tokens[i++];
|
|
26055
|
-
const resolved =
|
|
26056
|
-
if (resolved
|
|
26253
|
+
const resolved = findTech(tech.value);
|
|
26254
|
+
if (resolved?.type === "group") techStack = resolved.techStack;
|
|
26057
26255
|
else this.warning(`'${tech.value}' is not a group style; using ${DEFAULT_GROUP_TECH}`, this.locOf(tech, line));
|
|
26058
26256
|
}
|
|
26059
26257
|
let position;
|
|
@@ -26186,6 +26384,7 @@ var Parser = class {
|
|
|
26186
26384
|
const resolved = this.resolveTech(t, line);
|
|
26187
26385
|
node.type = resolved.type;
|
|
26188
26386
|
node.techStack = resolved.techStack;
|
|
26387
|
+
if (resolved.inferredKind) node.inferredKind = resolved.inferredKind;
|
|
26189
26388
|
node.kind = resolved.type === "text" ? "text" : resolved.type === "group" ? "group" : "component";
|
|
26190
26389
|
} else if (t.kind === "team") {
|
|
26191
26390
|
node.ownerTeam = t.value;
|
|
@@ -26540,12 +26739,13 @@ var Parser = class {
|
|
|
26540
26739
|
if (rps === void 0) return;
|
|
26541
26740
|
override.rps = rps;
|
|
26542
26741
|
i++;
|
|
26543
|
-
} else if (isWord(t, "latency") || isWord(t, "availability") || isWord(t, "cost")) {
|
|
26742
|
+
} else if (isWord(t, "latency") || isWord(t, "availability") || isWord(t, "cost") || isWord(t, "timeout")) {
|
|
26544
26743
|
part = t.value;
|
|
26545
|
-
const unit = part === "latency" ? "ms" : part === "availability" ? "%" : "usd/month";
|
|
26744
|
+
const unit = part === "latency" || part === "timeout" ? "ms" : part === "availability" ? "%" : "usd/month";
|
|
26546
26745
|
const value = this.quantityOf(tokens[i + 1], unit, t, line);
|
|
26547
26746
|
if (value === void 0) return;
|
|
26548
26747
|
if (part === "latency") override.latencyMs = value;
|
|
26748
|
+
else if (part === "timeout") override.timeoutMs = value;
|
|
26549
26749
|
else if (part === "availability") override.availability = value;
|
|
26550
26750
|
else override.costUsd = value;
|
|
26551
26751
|
i += 2;
|
|
@@ -26953,7 +27153,7 @@ var Parser = class {
|
|
|
26953
27153
|
parseSelectorAtom(tokens, i, line) {
|
|
26954
27154
|
const t = tokens[i];
|
|
26955
27155
|
if (t?.kind === "tech") {
|
|
26956
|
-
const tech =
|
|
27156
|
+
const tech = findTech(t.value)?.techStack ?? t.value;
|
|
26957
27157
|
return { selector: { tech }, next: i + 1, token: t, loc: this.locOf(t, line) };
|
|
26958
27158
|
}
|
|
26959
27159
|
if (isWord(t, "any")) {
|
|
@@ -26978,7 +27178,10 @@ var Parser = class {
|
|
|
26978
27178
|
/** Warns about a selector naming a tech stack that does not exist, and (once every file is read) a node that does not. */
|
|
26979
27179
|
checkSelectorLater({ atoms }) {
|
|
26980
27180
|
for (const { selector, token, loc } of atoms) {
|
|
26981
|
-
if ("tech" in selector && !
|
|
27181
|
+
if ("tech" in selector && !findTech(token.value)) {
|
|
27182
|
+
const suggestion = suggestTech(token.value);
|
|
27183
|
+
this.warning(`Unknown tech stack '${token.value}'${suggestion ? `. Did you mean '${suggestion}'?` : ""}`, loc);
|
|
27184
|
+
}
|
|
26982
27185
|
if ("node" in selector) {
|
|
26983
27186
|
this.deferred.push(() => {
|
|
26984
27187
|
if (!this.nodes.has(selector.node)) this.warning(`Unknown node '${selector.node}'`, loc);
|
|
@@ -27138,11 +27341,27 @@ var Parser = class {
|
|
|
27138
27341
|
where(first, later) {
|
|
27139
27342
|
return first.file === later.file ? `on line ${first.line}` : `in ${fileName(first.file ?? this.options.path ?? "the root document")} on line ${first.line}`;
|
|
27140
27343
|
}
|
|
27344
|
+
/**
|
|
27345
|
+
* A node's `[Tech]`: the catalog entry it names (name or alias), or for an
|
|
27346
|
+
* unknown tech the text as written, with a warning that names the closest
|
|
27347
|
+
* catalog tech. It is drawn and simulated as the kind its name suggests
|
|
27348
|
+
* (`TigerBeetle DB`: database), else the kind of that closest tech
|
|
27349
|
+
* (`Postgress`: database), else a service; never a free, infinitely fast
|
|
27350
|
+
* client.
|
|
27351
|
+
*/
|
|
27141
27352
|
resolveTech(token, line) {
|
|
27142
|
-
const
|
|
27143
|
-
if (
|
|
27144
|
-
|
|
27145
|
-
|
|
27353
|
+
const found = findTech(token.value);
|
|
27354
|
+
if (found) return { type: found.type, techStack: found.techStack };
|
|
27355
|
+
const text = token.value.trim();
|
|
27356
|
+
if (!text) {
|
|
27357
|
+
this.warning(`Empty tech stack; drawing a ${DEFAULT_TECH}`, this.locOf(token, line));
|
|
27358
|
+
return { type: "shape", techStack: DEFAULT_TECH };
|
|
27359
|
+
}
|
|
27360
|
+
const suggestion = suggestTech(text);
|
|
27361
|
+
const closest = suggestion && findTech(suggestion);
|
|
27362
|
+
const kind = kindFromName(text) ?? (closest ? kindOf({ kind: "component", type: closest.type, techStack: closest.techStack }) : void 0) ?? "service";
|
|
27363
|
+
this.warning(unknownTechMessage(text, kind, suggestion), this.locOf(token, line));
|
|
27364
|
+
return { type: TYPE_OF_KIND[kind], techStack: text, inferredKind: kind };
|
|
27146
27365
|
}
|
|
27147
27366
|
useCaseId(name) {
|
|
27148
27367
|
const base = slug(name) || "usecase";
|
|
@@ -27352,14 +27571,14 @@ ${text}
|
|
|
27352
27571
|
|
|
27353
27572
|
// ../frontend/src/dsl/format.ts
|
|
27354
27573
|
function format(source2) {
|
|
27355
|
-
const
|
|
27574
|
+
const entries2 = classify(source2.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l));
|
|
27356
27575
|
const out = [];
|
|
27357
27576
|
let run = [];
|
|
27358
27577
|
const flush = () => {
|
|
27359
27578
|
if (run.length) out.push(...renderRun(run));
|
|
27360
27579
|
run = [];
|
|
27361
27580
|
};
|
|
27362
|
-
tidyBlankLines(
|
|
27581
|
+
tidyBlankLines(entries2).forEach((entry) => {
|
|
27363
27582
|
const last = run[run.length - 1];
|
|
27364
27583
|
if (last && (entry.kind !== last.kind || entry.depth !== last.depth || last.kind === "connection" && last.continuation.length || last.kind === "cells" && entry.kind === "cells" && entry.section !== last.section))
|
|
27365
27584
|
flush();
|
|
@@ -27375,19 +27594,19 @@ function format(source2) {
|
|
|
27375
27594
|
var INDENT = " ";
|
|
27376
27595
|
var KEYWORDS = /* @__PURE__ */ new Set(["title", "import", "group", "usecase", "par", "alt"]);
|
|
27377
27596
|
function classify(lines) {
|
|
27378
|
-
const
|
|
27597
|
+
const entries2 = [];
|
|
27379
27598
|
let depth = 0;
|
|
27380
27599
|
let section;
|
|
27381
27600
|
for (let i = 0; i < lines.length; i++) {
|
|
27382
27601
|
const raw = lines[i];
|
|
27383
27602
|
const original = raw.trim();
|
|
27384
27603
|
if (!original) {
|
|
27385
|
-
|
|
27604
|
+
entries2.push({ kind: "blank", depth, text: "" });
|
|
27386
27605
|
continue;
|
|
27387
27606
|
}
|
|
27388
27607
|
const { tokens, diagnostics } = tokenizeLine(raw, i + 1);
|
|
27389
27608
|
if (diagnostics.some((d) => d.severity === "error") || tokens.length === 0) {
|
|
27390
|
-
|
|
27609
|
+
entries2.push({ kind: "line", depth, text: original });
|
|
27391
27610
|
continue;
|
|
27392
27611
|
}
|
|
27393
27612
|
const comment = commentOf(raw, tokens);
|
|
@@ -27396,7 +27615,7 @@ function classify(lines) {
|
|
|
27396
27615
|
const inSection = section !== void 0 && depth === 1 && first.kind !== "rbrace";
|
|
27397
27616
|
const cells = inSection ? sectionCells(section, raw, tokens) : void 0;
|
|
27398
27617
|
if (cells) {
|
|
27399
|
-
|
|
27618
|
+
entries2.push({ ...base, kind: "cells", section, cells });
|
|
27400
27619
|
continue;
|
|
27401
27620
|
}
|
|
27402
27621
|
if (!inSection && first.kind === "ident" && second?.kind === "arrow" && third?.kind === "ident" && tokens.length <= 4 && (!fourth || fourth.kind === "label")) {
|
|
@@ -27411,7 +27630,7 @@ function classify(lines) {
|
|
|
27411
27630
|
}
|
|
27412
27631
|
while (entry.continuation[entry.continuation.length - 1] === "") entry.continuation.pop();
|
|
27413
27632
|
}
|
|
27414
|
-
|
|
27633
|
+
entries2.push(entry);
|
|
27415
27634
|
continue;
|
|
27416
27635
|
}
|
|
27417
27636
|
const oneLineDecision = first.value === "decision" && second?.kind === "string" && third?.kind === "ident" && third.value === "because";
|
|
@@ -27419,19 +27638,19 @@ function classify(lines) {
|
|
|
27419
27638
|
let k = 1;
|
|
27420
27639
|
const take = (kind) => tokens[k]?.kind === kind ? source(raw, tokens[k++]) : "";
|
|
27421
27640
|
const cells2 = [first.value, take("string"), take("tech"), take("team"), join2(raw, tokens.slice(k))];
|
|
27422
|
-
|
|
27641
|
+
entries2.push({ ...base, kind: "node", cells: cells2 });
|
|
27423
27642
|
continue;
|
|
27424
27643
|
}
|
|
27425
27644
|
const closes = first.kind === "rbrace";
|
|
27426
27645
|
const opens = tokens[tokens.length - 1].kind === "lbrace";
|
|
27427
27646
|
const code = join2(raw, tokens);
|
|
27428
27647
|
const text = withComment(code, comment);
|
|
27429
|
-
|
|
27648
|
+
entries2.push({ kind: "line", depth: Math.max(0, depth - (closes ? 1 : 0)), text: sameTokens(text, tokens) ? text : original, opens, closes });
|
|
27430
27649
|
if (depth === 0) section = sectionOpenedBy(tokens);
|
|
27431
27650
|
for (const t of tokens) depth = Math.max(0, depth + (t.kind === "lbrace" ? 1 : t.kind === "rbrace" ? -1 : 0));
|
|
27432
27651
|
if (depth === 0) section = void 0;
|
|
27433
27652
|
}
|
|
27434
|
-
return
|
|
27653
|
+
return entries2;
|
|
27435
27654
|
}
|
|
27436
27655
|
function sectionOpenedBy(tokens) {
|
|
27437
27656
|
const [first, second] = tokens;
|
|
@@ -27465,9 +27684,9 @@ function sectionCells(section, raw, tokens) {
|
|
|
27465
27684
|
return void 0;
|
|
27466
27685
|
}
|
|
27467
27686
|
}
|
|
27468
|
-
function tidyBlankLines(
|
|
27687
|
+
function tidyBlankLines(entries2) {
|
|
27469
27688
|
const kept = [];
|
|
27470
|
-
for (const entry of
|
|
27689
|
+
for (const entry of entries2) {
|
|
27471
27690
|
if (entry.kind !== "blank") {
|
|
27472
27691
|
if (entry.kind === "line" && entry.closes) while (kept[kept.length - 1]?.kind === "blank") kept.pop();
|
|
27473
27692
|
kept.push(entry);
|
|
@@ -27699,6 +27918,8 @@ function accessIn(diagram) {
|
|
|
27699
27918
|
|
|
27700
27919
|
// ../frontend/src/sim/profiles.ts
|
|
27701
27920
|
var NEUTRAL = { rps: Infinity, latencyMs: 0, availability: 1, costUsd: 0, durable: false };
|
|
27921
|
+
var INTERNET_EGRESS_USD_PER_GB = 0.09;
|
|
27922
|
+
var CDN_EGRESS_USD_PER_GB = 0.02;
|
|
27702
27923
|
var RELATIONAL = {
|
|
27703
27924
|
rps: 2e4,
|
|
27704
27925
|
readRps: 2e4,
|
|
@@ -27712,8 +27933,9 @@ var RELATIONAL = {
|
|
|
27712
27933
|
};
|
|
27713
27934
|
var KIND_PROFILES = {
|
|
27714
27935
|
client: { ...NEUTRAL, bandwidthMBps: 10 },
|
|
27936
|
+
// A firewall or accelerator in front of everything else (WAF, Global Accelerator); `any edge` also selects the four below.
|
|
27715
27937
|
edge: { rps: 1e5, latencyMs: 2, availability: 0.9999, costUsd: 50, durable: false },
|
|
27716
|
-
cdn: { rps: 2e5, latencyMs: 5, availability: 0.9999, costUsd: 100, durable: false, egressUsdPerGb:
|
|
27938
|
+
cdn: { rps: 2e5, latencyMs: 5, availability: 0.9999, costUsd: 100, durable: false, egressUsdPerGb: CDN_EGRESS_USD_PER_GB },
|
|
27717
27939
|
loadbalancer: { rps: 1e5, latencyMs: 2, availability: 0.9999, costUsd: 50, durable: false },
|
|
27718
27940
|
gateway: { rps: 1e4, latencyMs: 10, availability: 0.9995, costUsd: 100, durable: false },
|
|
27719
27941
|
// Name resolution happens before the request and is cached: off the request path.
|
|
@@ -27725,23 +27947,16 @@ var KIND_PROFILES = {
|
|
|
27725
27947
|
search: { rps: 3e3, latencyMs: 15, availability: 0.999, costUsd: 400, durable: true, consistency: "eventual" },
|
|
27726
27948
|
analytics: { rps: 200, latencyMs: 500, availability: 0.999, costUsd: 300, durable: true, consistency: "eventual" },
|
|
27727
27949
|
queue: { rps: 5e4, latencyMs: 5, availability: 0.9999, costUsd: 200, durable: true, consistency: "strong" },
|
|
27728
|
-
storage: { rps: 5e3, latencyMs: 30, availability: 0.9999, costUsd: 50, durable: true, consistency: "eventual"
|
|
27950
|
+
storage: { rps: 5e3, latencyMs: 30, availability: 0.9999, costUsd: 50, durable: true, consistency: "eventual" },
|
|
27729
27951
|
external: { rps: 1e3, latencyMs: 200, availability: 0.999, costUsd: 0, durable: false },
|
|
27730
27952
|
other: NEUTRAL
|
|
27731
27953
|
};
|
|
27732
27954
|
var NOSQL = { rps: 2e4, latencyMs: 5, availability: 0.9999, costUsd: 500, durable: true, consistency: "eventual" };
|
|
27733
27955
|
var NOSQL_STRONG = { ...NOSQL, consistency: "strong" };
|
|
27734
|
-
var
|
|
27735
|
-
|
|
27736
|
-
"
|
|
27737
|
-
|
|
27738
|
-
CouchDB: NOSQL,
|
|
27739
|
-
"Azure Cosmos DB": NOSQL,
|
|
27740
|
-
MongoDB: NOSQL_STRONG,
|
|
27741
|
-
"GCP Bigtable": NOSQL_STRONG,
|
|
27742
|
-
"GCP Firestore": NOSQL_STRONG,
|
|
27743
|
-
"GCP Spanner": NOSQL_STRONG
|
|
27744
|
-
};
|
|
27956
|
+
var PROFILE_ROWS = { nosql: NOSQL, nosqlStrong: NOSQL_STRONG };
|
|
27957
|
+
var TECH_PROFILES = Object.fromEntries(
|
|
27958
|
+
componentCatalog.flatMap((c) => "profile" in c ? [[c.techStack, PROFILE_ROWS[c.profile]]] : [])
|
|
27959
|
+
);
|
|
27745
27960
|
var BANDWIDTH = {
|
|
27746
27961
|
client: 10,
|
|
27747
27962
|
edge: 1e3,
|
|
@@ -27752,9 +27967,10 @@ var BANDWIDTH = {
|
|
|
27752
27967
|
service: 200
|
|
27753
27968
|
};
|
|
27754
27969
|
var DEFAULT_BANDWIDTH_MBPS = 100;
|
|
27970
|
+
var CHARGES_EGRESS = (kind) => kind !== "client" && kind !== "external" && kind !== "dns" && kind !== "other";
|
|
27755
27971
|
function profileOf(node, override) {
|
|
27756
27972
|
const kind = kindOf(node);
|
|
27757
|
-
const base = TECH_PROFILES[node.techStack] ?? KIND_PROFILES[kind];
|
|
27973
|
+
const base = (Object.hasOwn(TECH_PROFILES, node.techStack) ? TECH_PROFILES[node.techStack] : void 0) ?? KIND_PROFILES[kind];
|
|
27758
27974
|
const baseRead = base.readRps ?? base.rps;
|
|
27759
27975
|
const baseWrite = base.writeRps ?? base.rps;
|
|
27760
27976
|
const readRps = override?.readRps ?? override?.rps ?? baseRead;
|
|
@@ -27773,7 +27989,8 @@ function profileOf(node, override) {
|
|
|
27773
27989
|
durable: override?.durable ?? base.durable,
|
|
27774
27990
|
...consistency && isDataStore(kind) ? { consistency } : {},
|
|
27775
27991
|
bandwidthMBps: override?.bandwidthMBps ?? base.bandwidthMBps ?? BANDWIDTH[kind] ?? DEFAULT_BANDWIDTH_MBPS,
|
|
27776
|
-
egressUsdPerGb: override?.egressUsdPerGb ?? base.egressUsdPerGb ?? 0
|
|
27992
|
+
egressUsdPerGb: override?.egressUsdPerGb ?? base.egressUsdPerGb ?? (CHARGES_EGRESS(kind) ? INTERNET_EGRESS_USD_PER_GB : 0),
|
|
27993
|
+
...override?.timeoutMs !== void 0 ? { timeoutMs: override.timeoutMs } : {}
|
|
27777
27994
|
};
|
|
27778
27995
|
}
|
|
27779
27996
|
function capacityOf(p, replicas) {
|
|
@@ -27926,10 +28143,11 @@ function formatUsd(usd) {
|
|
|
27926
28143
|
var DEFAULT_TIMEOUT_MS = 1e3;
|
|
27927
28144
|
var HOT = 0.7;
|
|
27928
28145
|
var QUEUE_CAP = 0.95;
|
|
28146
|
+
var FAILOVER_SHARE = 0.1;
|
|
28147
|
+
var SERVICE_SPREAD = 0.5;
|
|
27929
28148
|
var SECONDS_PER_MONTH = 2592e3;
|
|
27930
28149
|
var BYTES_PER_MB = 1e6;
|
|
27931
28150
|
var BYTES_PER_GB = 1e9;
|
|
27932
|
-
var PERCENTILE_FACTORS = { 50: 1, 90: 1.6, 95: 2, 99: 3, 99.9: 5 };
|
|
27933
28151
|
var PERCENTILE_KEYS = { 50: "p50", 90: "p90", 95: "p95", 99: "p99", 99.9: "p999" };
|
|
27934
28152
|
var PERCENTILES2 = Object.keys(PERCENTILE_KEYS).map(Number);
|
|
27935
28153
|
function components(diagram) {
|
|
@@ -27970,7 +28188,9 @@ function forEachStepRate(diagram, traffic, visit) {
|
|
|
27970
28188
|
const { rps, shares } = traffic.get(u.id) ?? { rps: 0, shares: [] };
|
|
27971
28189
|
u.scenarios.forEach((s, i) => {
|
|
27972
28190
|
const rate = rps * (shares[i] ?? 0);
|
|
27973
|
-
if (rate > 0)
|
|
28191
|
+
if (rate > 0) {
|
|
28192
|
+
for (const step of s.steps) if (!step.failed) visit(step, rate);
|
|
28193
|
+
}
|
|
27974
28194
|
});
|
|
27975
28195
|
}
|
|
27976
28196
|
}
|
|
@@ -27984,35 +28204,76 @@ function computeLoad(diagram, traffic) {
|
|
|
27984
28204
|
});
|
|
27985
28205
|
return load;
|
|
27986
28206
|
}
|
|
27987
|
-
var
|
|
27988
|
-
|
|
28207
|
+
var payloadDirection = (step, access) => access === "read" ? { from: step.toServiceId, to: step.fromServiceId } : { from: step.fromServiceId, to: step.toServiceId };
|
|
28208
|
+
var OUTSIDE = (kind) => kind === "client" || kind === "external";
|
|
28209
|
+
function computeEgressGb(diagram, traffic, kindOf2 = (id) => profileOfId(diagram, id)) {
|
|
27989
28210
|
const gb = /* @__PURE__ */ new Map();
|
|
27990
28211
|
const access = accessIn(diagram);
|
|
27991
28212
|
forEachStepRate(diagram, traffic, (step, rate) => {
|
|
27992
28213
|
if (!step.sizeBytes || step.sizeBytes <= 0) return;
|
|
27993
|
-
const
|
|
27994
|
-
|
|
28214
|
+
const { from, to } = payloadDirection(step, access(step));
|
|
28215
|
+
if (OUTSIDE(kindOf2(from)) || !OUTSIDE(kindOf2(to))) return;
|
|
28216
|
+
gb.set(from, (gb.get(from) ?? 0) + rate * multiplierOf(step) * step.sizeBytes * SECONDS_PER_MONTH / BYTES_PER_GB);
|
|
27995
28217
|
});
|
|
27996
28218
|
return gb;
|
|
27997
28219
|
}
|
|
28220
|
+
var BANDWIDTH_BOUND = (kind) => !["client", "external", "dns", "other", "storage", "cdn"].includes(kind);
|
|
28221
|
+
function computeBytesPerSecond(diagram, traffic) {
|
|
28222
|
+
const bytes = /* @__PURE__ */ new Map();
|
|
28223
|
+
forEachStepRate(diagram, traffic, (step, rate) => {
|
|
28224
|
+
if (!step.sizeBytes || step.sizeBytes <= 0) return;
|
|
28225
|
+
const b = rate * multiplierOf(step) * step.sizeBytes;
|
|
28226
|
+
for (const id of /* @__PURE__ */ new Set([step.fromServiceId, step.toServiceId])) bytes.set(id, (bytes.get(id) ?? 0) + b);
|
|
28227
|
+
});
|
|
28228
|
+
return bytes;
|
|
28229
|
+
}
|
|
28230
|
+
function profileOfId(diagram, id) {
|
|
28231
|
+
const node = diagram.nodes.find((n) => n.id === id);
|
|
28232
|
+
return node ? profileOf(node).kind : "client";
|
|
28233
|
+
}
|
|
27998
28234
|
function transferMs(step, bandwidthOf) {
|
|
27999
28235
|
if (!step.sizeBytes || step.sizeBytes <= 0) return 0;
|
|
28000
28236
|
const mbps = Math.min(bandwidthOf(step.fromServiceId), bandwidthOf(step.toServiceId));
|
|
28001
|
-
return mbps > 0 && Number.isFinite(mbps) ? step.sizeBytes / BYTES_PER_MB / mbps * 1e3 : 0;
|
|
28237
|
+
return mbps > 0 && Number.isFinite(mbps) ? multiplierOf(step) * step.sizeBytes / BYTES_PER_MB / mbps * 1e3 : 0;
|
|
28002
28238
|
}
|
|
28003
28239
|
var utilizationOf = (load, capacity) => Number.isFinite(capacity) && capacity > 0 ? load / capacity : 0;
|
|
28004
|
-
|
|
28240
|
+
function erlangC(servers, rho) {
|
|
28241
|
+
if (rho <= 0) return 0;
|
|
28242
|
+
if (rho >= 1) return 1;
|
|
28243
|
+
const c = Math.max(1, Math.round(servers));
|
|
28244
|
+
const a = c * rho;
|
|
28245
|
+
let b = 1;
|
|
28246
|
+
for (let k = 1; k <= c; k++) b = a * b / (k + a * b);
|
|
28247
|
+
return b / (1 - rho * (1 - b));
|
|
28248
|
+
}
|
|
28249
|
+
function hopModel(baseMs, rho, servers = 1) {
|
|
28250
|
+
const r = Math.min(Math.max(rho, 0), QUEUE_CAP);
|
|
28251
|
+
const c = Math.max(1, Math.round(servers));
|
|
28252
|
+
const waitProbability = erlangC(c, r);
|
|
28253
|
+
const meanMs = baseMs + waitProbability * baseMs / (c * (1 - r));
|
|
28254
|
+
const fixedMs = (1 - SERVICE_SPREAD) * baseMs;
|
|
28255
|
+
return { fixedMs, tailMs: meanMs - fixedMs, waitProbability };
|
|
28256
|
+
}
|
|
28257
|
+
var hopMean = (h) => h.fixedMs + h.tailMs;
|
|
28258
|
+
var tailFactor = (p) => -Math.log(1 - p);
|
|
28005
28259
|
var replicatedAvailability = (a, replicas) => 1 - (1 - a) ** replicas;
|
|
28006
|
-
|
|
28260
|
+
var primaryAvailability = (a, replicas) => replicas >= 2 ? 1 - (1 - a) * FAILOVER_SHARE : a;
|
|
28261
|
+
function usageOf(p, replicas, readLoadRps, writeLoadRps, bytesPerSecond = 0) {
|
|
28007
28262
|
const { readRps: readCapacityRps, writeRps: writeCapacityRps } = capacityOf(p, replicas);
|
|
28008
28263
|
const readUtilization = utilizationOf(readLoadRps, readCapacityRps);
|
|
28009
28264
|
const writeUtilization = utilizationOf(writeLoadRps, writeCapacityRps);
|
|
28010
|
-
const
|
|
28265
|
+
const requestUtilization = combinedUtilization(p, readUtilization, writeUtilization);
|
|
28011
28266
|
const loadRps = readLoadRps + writeLoadRps;
|
|
28267
|
+
const bound = BANDWIDTH_BOUND(p.kind);
|
|
28268
|
+
const bandwidthLoadMBps = bound ? bytesPerSecond / BYTES_PER_MB : 0;
|
|
28269
|
+
const bandwidthCapacityMBps = bound ? p.bandwidthMBps * replicas * p.shards : 0;
|
|
28270
|
+
const bandwidthUtilization = utilizationOf(bandwidthLoadMBps, bandwidthCapacityMBps);
|
|
28271
|
+
const utilization = Math.max(requestUtilization, bandwidthUtilization);
|
|
28012
28272
|
return {
|
|
28013
28273
|
loadRps,
|
|
28014
|
-
capacityRps: readCapacityRps === writeCapacityRps ||
|
|
28274
|
+
capacityRps: readCapacityRps === writeCapacityRps || requestUtilization === 0 ? readCapacityRps : loadRps / requestUtilization,
|
|
28015
28275
|
utilization,
|
|
28276
|
+
requestUtilization,
|
|
28016
28277
|
saturated: utilization >= 1,
|
|
28017
28278
|
readLoadRps,
|
|
28018
28279
|
writeLoadRps,
|
|
@@ -28020,27 +28281,68 @@ function usageOf(p, replicas, readLoadRps, writeLoadRps) {
|
|
|
28020
28281
|
writeCapacityRps,
|
|
28021
28282
|
readUtilization,
|
|
28022
28283
|
writeUtilization,
|
|
28284
|
+
bandwidthLoadMBps,
|
|
28285
|
+
bandwidthCapacityMBps,
|
|
28286
|
+
bandwidthUtilization,
|
|
28023
28287
|
writeScaling: p.writeScaling
|
|
28024
28288
|
};
|
|
28025
28289
|
}
|
|
28290
|
+
var bandwidthBound = (n) => n.bandwidthUtilization > n.requestUtilization;
|
|
28291
|
+
var serversOf = (n, replicas) => writeBound(n) ? 1 : replicas;
|
|
28026
28292
|
function usageText(n) {
|
|
28027
28293
|
const part = (load, capacity, rho) => `${formatRps(load)} of ${formatRps(capacity)}, ${formatPercent(rho)}`;
|
|
28294
|
+
if (bandwidthBound(n)) {
|
|
28295
|
+
const mb = (x) => `${Number(x.toPrecision(3)).toLocaleString("en-US")} MB/s`;
|
|
28296
|
+
return `bandwidth ${mb(n.bandwidthLoadMBps)} of ${mb(n.bandwidthCapacityMBps)}, ${formatPercent(n.bandwidthUtilization)}`;
|
|
28297
|
+
}
|
|
28028
28298
|
if (n.writeScaling === "shards" && n.writeLoadRps > 0) {
|
|
28029
28299
|
return `reads ${part(n.readLoadRps, n.readCapacityRps, n.readUtilization)}; writes ${part(n.writeLoadRps, n.writeCapacityRps, n.writeUtilization)}`;
|
|
28030
28300
|
}
|
|
28031
|
-
return part(n.loadRps, n.capacityRps, n.
|
|
28301
|
+
return part(n.loadRps, n.capacityRps, n.requestUtilization);
|
|
28032
28302
|
}
|
|
28033
28303
|
var writeBound = (n) => n.writeScaling === "shards" && n.writeUtilization >= n.readUtilization && n.writeLoadRps > 0;
|
|
28034
|
-
function
|
|
28035
|
-
|
|
28036
|
-
|
|
28037
|
-
|
|
28038
|
-
let
|
|
28039
|
-
for (const
|
|
28040
|
-
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
28304
|
+
function pathMean(path) {
|
|
28305
|
+
return path.parts.reduce((sum, part) => sum + Math.max(0, ...part.map((i) => i.fixedMs + i.tailMs)), 0);
|
|
28306
|
+
}
|
|
28307
|
+
function pathAt(path, l) {
|
|
28308
|
+
let sum = 0;
|
|
28309
|
+
for (const part of path.parts) {
|
|
28310
|
+
let most = 0;
|
|
28311
|
+
for (const i of part) most = Math.max(most, i.fixedMs + i.tailMs * l);
|
|
28312
|
+
sum += most;
|
|
28313
|
+
}
|
|
28314
|
+
return sum;
|
|
28315
|
+
}
|
|
28316
|
+
var pathQuantile = (path, p) => pathAt(path, tailFactor(p));
|
|
28317
|
+
function pathCdf(path, t) {
|
|
28318
|
+
if (pathAt(path, 0) > t) return 0;
|
|
28319
|
+
let hi = 1;
|
|
28320
|
+
while (pathAt(path, hi) <= t) {
|
|
28321
|
+
hi *= 2;
|
|
28322
|
+
if (hi > 1e6) return 1;
|
|
28323
|
+
}
|
|
28324
|
+
let lo = 0;
|
|
28325
|
+
for (let i = 0; i < 50; i++) {
|
|
28326
|
+
const mid = (lo + hi) / 2;
|
|
28327
|
+
if (pathAt(path, mid) <= t) lo = mid;
|
|
28328
|
+
else hi = mid;
|
|
28329
|
+
}
|
|
28330
|
+
return 1 - Math.exp(-lo);
|
|
28331
|
+
}
|
|
28332
|
+
function mixtureQuantile(paths, q) {
|
|
28333
|
+
const live = paths.filter((s) => s.share > 0);
|
|
28334
|
+
if (live.length === 0) return 0;
|
|
28335
|
+
if (live.length === 1) return pathQuantile(live[0].path, q);
|
|
28336
|
+
const total = live.reduce((sum, s) => sum + s.share, 0);
|
|
28337
|
+
const cdf = (t) => live.reduce((sum, s) => sum + s.share * pathCdf(s.path, t), 0) / total;
|
|
28338
|
+
let lo = Math.min(...live.map((s) => pathQuantile(s.path, 0)));
|
|
28339
|
+
let hi = Math.max(...live.map((s) => pathQuantile(s.path, q)));
|
|
28340
|
+
for (let i = 0; i < 60 && hi - lo > 1e-9 * Math.max(1, hi); i++) {
|
|
28341
|
+
const mid = (lo + hi) / 2;
|
|
28342
|
+
if (cdf(mid) >= q) hi = mid;
|
|
28343
|
+
else lo = mid;
|
|
28344
|
+
}
|
|
28345
|
+
return hi;
|
|
28044
28346
|
}
|
|
28045
28347
|
function mainScenarioIndex(shares) {
|
|
28046
28348
|
let best = 0;
|
|
@@ -28057,20 +28359,27 @@ function profilesOf(diagram, warnings = []) {
|
|
|
28057
28359
|
return new Map(nodes.map((n) => [n.id, profileOf(n, overrides.get(n.id))]));
|
|
28058
28360
|
}
|
|
28059
28361
|
function analyze(diagram, options = {}) {
|
|
28060
|
-
const
|
|
28362
|
+
const defaultTimeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
28061
28363
|
const warnings = [];
|
|
28062
28364
|
const nodes = components(diagram);
|
|
28063
28365
|
const profiles = profilesOf(diagram, warnings);
|
|
28366
|
+
const kindOf2 = (id) => profiles.get(id)?.kind ?? "client";
|
|
28064
28367
|
const traffic = resolveTraffic(diagram, warnings);
|
|
28065
28368
|
const load = computeLoad(diagram, traffic);
|
|
28066
|
-
const egress = computeEgressGb(diagram, traffic);
|
|
28369
|
+
const egress = computeEgressGb(diagram, traffic, kindOf2);
|
|
28370
|
+
const bytes = computeBytesPerSecond(diagram, traffic);
|
|
28371
|
+
const hops = /* @__PURE__ */ new Map();
|
|
28067
28372
|
const nodeResults = nodes.map((n) => {
|
|
28068
28373
|
const p = profiles.get(n.id);
|
|
28069
|
-
const replicas = replicasOf(n);
|
|
28374
|
+
const replicas = Math.max(1, options.replicas?.get(n.id) ?? replicasOf(n));
|
|
28070
28375
|
const { read, write } = load.get(n.id) ?? { read: 0, write: 0 };
|
|
28071
|
-
const usage = usageOf(p, replicas, read, write);
|
|
28376
|
+
const usage = usageOf(p, replicas, read, write, bytes.get(n.id) ?? 0);
|
|
28377
|
+
const servers = serversOf(usage, replicas);
|
|
28378
|
+
const hop = hopModel(p.latencyMs, usage.utilization, servers);
|
|
28379
|
+
hops.set(n.id, hop);
|
|
28072
28380
|
const egressGbPerMonth = egress.get(n.id) ?? 0;
|
|
28073
28381
|
const egressUsd = egressGbPerMonth * p.egressUsdPerGb;
|
|
28382
|
+
const availability2 = replicatedAvailability(p.availability, replicas);
|
|
28074
28383
|
return {
|
|
28075
28384
|
id: n.id,
|
|
28076
28385
|
kind: p.kind,
|
|
@@ -28078,8 +28387,11 @@ function analyze(diagram, options = {}) {
|
|
|
28078
28387
|
shards: p.shards,
|
|
28079
28388
|
...usage,
|
|
28080
28389
|
...p.consistency ? { consistency: p.consistency } : {},
|
|
28081
|
-
|
|
28082
|
-
|
|
28390
|
+
servers,
|
|
28391
|
+
waitProbability: hop.waitProbability,
|
|
28392
|
+
latencyMs: hopMean(hop),
|
|
28393
|
+
availability: availability2,
|
|
28394
|
+
writeAvailability: p.writeScaling === "shards" ? primaryAvailability(p.availability, replicas) : availability2,
|
|
28083
28395
|
costUsd: replicas * p.shards * p.costUsd + egressUsd,
|
|
28084
28396
|
egressGbPerMonth,
|
|
28085
28397
|
egressUsd,
|
|
@@ -28087,28 +28399,49 @@ function analyze(diagram, options = {}) {
|
|
|
28087
28399
|
};
|
|
28088
28400
|
});
|
|
28089
28401
|
const nodeById = new Map(nodeResults.map((n) => [n.id, n]));
|
|
28090
|
-
const hopMs = (id) => nodeById.get(id)?.latencyMs ?? 0;
|
|
28091
|
-
const availabilityOf = (id) => nodeById.get(id)?.availability ?? 1;
|
|
28092
28402
|
const bandwidthOf = (id) => profiles.get(id)?.bandwidthMBps ?? Infinity;
|
|
28403
|
+
const timeoutOf = (id) => profiles.get(id)?.timeoutMs ?? defaultTimeoutMs;
|
|
28093
28404
|
for (const n of nodeResults) {
|
|
28094
28405
|
const usage = usageText(n);
|
|
28095
28406
|
if (n.saturated) warnings.push(`'${n.id}' is saturated: ${usage}. ${writeBound(n) ? "Add shards for writes" : "Add replicas"} or take load off it.`);
|
|
28096
28407
|
else if (n.utilization > HOT) warnings.push(`'${n.id}' runs hot: ${usage}.`);
|
|
28097
28408
|
}
|
|
28409
|
+
const access = accessIn(diagram);
|
|
28410
|
+
const pathOf2 = (hopsOfPath) => ({
|
|
28411
|
+
parts: hopsOfPath.map(
|
|
28412
|
+
(part) => part.map((h) => {
|
|
28413
|
+
if (h.failed) return { fixedMs: timeoutOf(h.target), tailMs: 0 };
|
|
28414
|
+
const hop = hops.get(h.target) ?? { fixedMs: 0, tailMs: 0 };
|
|
28415
|
+
return { fixedMs: transferMs(h.step, bandwidthOf) + hop.fixedMs, tailMs: hop.tailMs };
|
|
28416
|
+
})
|
|
28417
|
+
)
|
|
28418
|
+
});
|
|
28098
28419
|
const useCases = diagram.useCases.map((u) => {
|
|
28099
28420
|
const { rps, shares } = traffic.get(u.id);
|
|
28100
|
-
const
|
|
28101
|
-
|
|
28102
|
-
|
|
28103
|
-
|
|
28104
|
-
|
|
28105
|
-
|
|
28106
|
-
|
|
28107
|
-
|
|
28108
|
-
|
|
28421
|
+
const paths = u.scenarios.map((s) => pathOf2(criticalPath(u, s)));
|
|
28422
|
+
const scenarios = u.scenarios.map((s, i) => ({
|
|
28423
|
+
id: s.id,
|
|
28424
|
+
name: s.name,
|
|
28425
|
+
share: shares[i] ?? 0,
|
|
28426
|
+
meanMs: pathMean(paths[i]),
|
|
28427
|
+
percentiles: Object.fromEntries(PERCENTILES2.map((q) => [PERCENTILE_KEYS[q], pathQuantile(paths[i], q / 100)]))
|
|
28428
|
+
}));
|
|
28429
|
+
const weighted = paths.map((path, i) => ({ share: shares[i] ?? 0, path }));
|
|
28430
|
+
const percentiles = Object.fromEntries(PERCENTILES2.map((q) => [PERCENTILE_KEYS[q], mixtureQuantile(weighted, q / 100)]));
|
|
28431
|
+
const tailScenario = Object.fromEntries(
|
|
28432
|
+
PERCENTILES2.map((q) => {
|
|
28433
|
+
const t = percentiles[PERCENTILE_KEYS[q]];
|
|
28434
|
+
const above = weighted.map((s) => s.share > 0 ? s.share * (1 - pathCdf(s.path, t * (1 - 1e-9))) : -1);
|
|
28435
|
+
return [PERCENTILE_KEYS[q], Math.max(0, above.indexOf(Math.max(...above)))];
|
|
28436
|
+
})
|
|
28109
28437
|
);
|
|
28110
28438
|
if (rps > 0 && u.scenarios.length === 0) warnings.push(`"${u.name}" has traffic but no steps`);
|
|
28111
|
-
|
|
28439
|
+
const writes = mainWrites(u, shares, (step) => access(step) === "write");
|
|
28440
|
+
const availabilityOf = (id) => {
|
|
28441
|
+
const n = nodeById.get(id);
|
|
28442
|
+
return !n ? 1 : writes.has(id) ? n.writeAvailability : n.availability;
|
|
28443
|
+
};
|
|
28444
|
+
return { id: u.id, name: u.name, rps, scenarios, percentiles, tailScenario, availability: useCaseAvailability(u, shares, availabilityOf) };
|
|
28112
28445
|
});
|
|
28113
28446
|
return {
|
|
28114
28447
|
nodes: nodeResults,
|
|
@@ -28119,8 +28452,10 @@ function analyze(diagram, options = {}) {
|
|
|
28119
28452
|
warnings
|
|
28120
28453
|
};
|
|
28121
28454
|
}
|
|
28122
|
-
function
|
|
28123
|
-
|
|
28455
|
+
function mainWrites(useCase, shares, isWrite) {
|
|
28456
|
+
if (useCase.scenarios.length === 0) return /* @__PURE__ */ new Set();
|
|
28457
|
+
const path = criticalPath(useCase, useCase.scenarios[mainScenarioIndex(shares)]).flat();
|
|
28458
|
+
return new Set(path.filter((h) => !h.failed && !h.async && isWrite(h.step)).map((h) => h.target));
|
|
28124
28459
|
}
|
|
28125
28460
|
function useCaseAvailability(useCase, shares, availabilityOf) {
|
|
28126
28461
|
if (useCase.scenarios.length === 0) return 1;
|
|
@@ -28147,9 +28482,9 @@ function singlePointsOfFailure(diagram) {
|
|
|
28147
28482
|
// ../frontend/src/sim/tests.ts
|
|
28148
28483
|
var pass = (message) => ({ passed: true, message });
|
|
28149
28484
|
var fail = (message, hint) => ({ passed: false, message, hint });
|
|
28150
|
-
function
|
|
28485
|
+
function makeRun(diagram, analysis) {
|
|
28151
28486
|
const access = accessIn(diagram);
|
|
28152
|
-
|
|
28487
|
+
return {
|
|
28153
28488
|
diagram,
|
|
28154
28489
|
analysis,
|
|
28155
28490
|
nodes: new Map(analysis.nodes.map((n) => [n.id, n])),
|
|
@@ -28158,6 +28493,9 @@ function runTests(diagram, analysis = analyze(diagram)) {
|
|
|
28158
28493
|
profiles: profilesOf(diagram),
|
|
28159
28494
|
isWrite: (step) => access(step) === "write"
|
|
28160
28495
|
};
|
|
28496
|
+
}
|
|
28497
|
+
function runTests(diagram, analysis = analyze(diagram)) {
|
|
28498
|
+
const run = makeRun(diagram, analysis);
|
|
28161
28499
|
const results = (diagram.requirements ?? []).map((r, i) => {
|
|
28162
28500
|
const { passed, message, hint } = requirement(run, r);
|
|
28163
28501
|
return { id: `req:${i + 1}`, name: requirementName(r), category: CATEGORY[r.kind], passed, message, hint, loc: r.loc };
|
|
@@ -28249,7 +28587,7 @@ function latency(run, u, q, maxMs) {
|
|
|
28249
28587
|
const value = `${key} of ${u.name} is ${formatMs(ms)}${result.rps > 0 ? "" : " with no traffic"}`;
|
|
28250
28588
|
const message = `${value} (limit ${formatMs(maxMs)})`;
|
|
28251
28589
|
if (ms < maxMs) return { ...pass(message), value };
|
|
28252
|
-
const index = Math.
|
|
28590
|
+
const index = Math.min(result.tailScenario[key] ?? 0, u.scenarios.length - 1);
|
|
28253
28591
|
const lead = u.scenarios.length > 1 ? `The "${u.scenarios[index].name}" path (${formatPercent(result.scenarios[index].share)} of traffic) sets ${key}. ` : "";
|
|
28254
28592
|
return { ...fail(message, lead + latencyHint(run, u, u.scenarios[index])), value };
|
|
28255
28593
|
}
|
|
@@ -28268,6 +28606,10 @@ function scenarioLatency(run, useCase, scenario, q, maxMs) {
|
|
|
28268
28606
|
}
|
|
28269
28607
|
function saturationHint(run, n) {
|
|
28270
28608
|
const label = nodeLabel(run.diagram, n.id);
|
|
28609
|
+
if (bandwidthBound(n)) {
|
|
28610
|
+
const replicas2 = Math.ceil(n.bandwidthUtilization * n.replicas / HOT);
|
|
28611
|
+
return `${label} moves more payload bytes than its network carries. Send the bytes around it (clients upload to and download from object storage or a CDN directly, with presigned URLs), or add replicas: x${replicas2} keeps its bandwidth under ${formatPercent(HOT)}`;
|
|
28612
|
+
}
|
|
28271
28613
|
if (writeBound(n)) {
|
|
28272
28614
|
const perShard = n.writeCapacityRps / n.shards;
|
|
28273
28615
|
const shards = Math.ceil(n.writeLoadRps / (perShard * HOT));
|
|
@@ -28279,11 +28621,12 @@ function saturationHint(run, n) {
|
|
|
28279
28621
|
}
|
|
28280
28622
|
function latencyHint(run, u, scenario) {
|
|
28281
28623
|
const hops = criticalPath(u, scenario).flat();
|
|
28282
|
-
const
|
|
28624
|
+
const timeout = (id) => run.profiles.get(id)?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
28625
|
+
const cost2 = (h) => h.failed ? timeout(h.target) : run.nodes.get(h.target)?.latencyMs ?? 0;
|
|
28283
28626
|
const slowest = hops.reduce((best, h) => !best || cost2(h) > cost2(best) ? h : best, void 0);
|
|
28284
28627
|
if (!slowest) return "Shorten the synchronous path of the request.";
|
|
28285
28628
|
const label = nodeLabel(run.diagram, slowest.target);
|
|
28286
|
-
if (slowest.failed) return `It waits ${formatMs(
|
|
28629
|
+
if (slowest.failed) return `It waits ${formatMs(timeout(slowest.target))} for the failed call to ${label}; give that path a smaller share of traffic, avoid the call, or time out sooner (capacity { ${slowest.target} timeout 200ms }).`;
|
|
28287
28630
|
const sized = hops.find((h) => (h.step.sizeBytes ?? 0) > 0);
|
|
28288
28631
|
const node = run.nodes.get(slowest.target);
|
|
28289
28632
|
if (node && node.utilization > HOT) return `${label} runs at ${formatPercent(node.utilization)} and queues requests; add replicas to it.`;
|
|
@@ -28297,8 +28640,11 @@ function availability(run, u, minPercent) {
|
|
|
28297
28640
|
if (result.availability * 100 >= minPercent - 1e-9) return { ...pass(message), value };
|
|
28298
28641
|
const shares = run.shares.get(u.id) ?? [];
|
|
28299
28642
|
const main = u.scenarios.length ? pathNodes(u, u.scenarios[mainScenarioIndex(shares)]) : [];
|
|
28300
|
-
const
|
|
28301
|
-
const
|
|
28643
|
+
const writes = mainWrites(u, shares, run.isWrite);
|
|
28644
|
+
const effective = (n) => writes.has(n.id) ? n.writeAvailability : n.availability;
|
|
28645
|
+
const weakest = main.filter((id) => fallbacksFor(u, id).length === 0).map((id) => run.nodes.get(id)).filter((n) => !!n && effective(n) < 1).sort((a, b) => effective(a) - effective(b))[0];
|
|
28646
|
+
const primaryBound = weakest && writes.has(weakest.id) && weakest.writeAvailability < weakest.availability;
|
|
28647
|
+
const hint = !weakest ? "Add replicas to the nodes on the main path, or fallback scenarios that complete without them" : primaryBound ? `The weakest link is writing to ${nodeLabel(run.diagram, weakest.id)}: writes need its primary, ${formatAvailability(weakest.writeAvailability)} with failover${weakest.replicas < 2 ? " (none without a second replica; add one)" : ` (a replica takes over, but about ${formatPercent(FAILOVER_SHARE)} of each outage is lost)`}. Move the writes to a partitioned store, or add a fallback scenario that calls it with -x and still completes` : `The weakest link is ${nodeLabel(run.diagram, weakest.id)} at ${formatAvailability(effective(weakest))}: add a replica (x${weakest.replicas + 1}) or a fallback scenario that calls it with -x and still completes`;
|
|
28302
28648
|
return { ...fail(message, hint), value };
|
|
28303
28649
|
}
|
|
28304
28650
|
function writeGap(run, u, s, accept) {
|
|
@@ -28356,20 +28702,34 @@ function survive(run, target) {
|
|
|
28356
28702
|
if (selected.length === 0) {
|
|
28357
28703
|
return target === "any" ? pass("No nodes to lose") : fail(`No node matches ${what}`, "Use a node id, [Tech] or any <kind>");
|
|
28358
28704
|
}
|
|
28705
|
+
const latencies = (run.diagram.requirements ?? []).filter((r) => r.kind === "latency" && requirement(run, r).passed);
|
|
28359
28706
|
const failures = [];
|
|
28360
28707
|
for (const node of selected) {
|
|
28361
28708
|
const n = run.nodes.get(node.id);
|
|
28362
28709
|
const label = nodeLabel(run.diagram, node.id);
|
|
28363
28710
|
const replicas = replicasOf(node);
|
|
28364
28711
|
if (replicas >= 2) {
|
|
28365
|
-
const
|
|
28712
|
+
const degraded = makeRun(run.diagram, analyze(run.diagram, { replicas: /* @__PURE__ */ new Map([[node.id, replicas - 1]]) }));
|
|
28713
|
+
const left = degraded.nodes.get(node.id);
|
|
28714
|
+
const lost = n.shards > 1 ? `one of the ${replicas} replicas of a ${node.id} shard` : `one of ${replicas} ${node.id} replicas`;
|
|
28366
28715
|
if (left.saturated) {
|
|
28716
|
+
const leaves = bandwidthBound(left) ? `leaves ${usageText(left)}` : n.shards > 1 ? `leaves that shard ${formatRps(left.capacityRps / n.shards)} for ${formatRps(n.loadRps / n.shards)}` : `leaves ${formatRps(left.capacityRps)} for ${formatRps(n.loadRps)}`;
|
|
28367
28717
|
failures.push(
|
|
28368
28718
|
fail(
|
|
28369
|
-
`Losing
|
|
28719
|
+
`Losing ${lost} ${leaves}${bandwidthBound(left) ? "" : ` (${formatPercent(left.utilization)})`}`,
|
|
28370
28720
|
writeBound(left) ? saturationHint(run, { ...n, ...left }) : `Add a replica to ${label} (x${replicas + 1}) so the rest carry the load`
|
|
28371
28721
|
)
|
|
28372
28722
|
);
|
|
28723
|
+
continue;
|
|
28724
|
+
}
|
|
28725
|
+
const broken = latencies.map((r) => requirement(degraded, r)).find((c) => !c.passed);
|
|
28726
|
+
if (broken) {
|
|
28727
|
+
failures.push(
|
|
28728
|
+
fail(
|
|
28729
|
+
`Losing ${lost} breaks a latency limit: ${broken.message}`,
|
|
28730
|
+
`Add a replica to ${label} (x${replicas + 1}) so the rest keep it under ${formatPercent(HOT)}, or take load off it`
|
|
28731
|
+
)
|
|
28732
|
+
);
|
|
28373
28733
|
}
|
|
28374
28734
|
continue;
|
|
28375
28735
|
}
|
|
@@ -28631,16 +28991,16 @@ function startsAt(run, a) {
|
|
|
28631
28991
|
const ids = idsOf(run, a.target);
|
|
28632
28992
|
const what = selectorText(a.target);
|
|
28633
28993
|
if (ids.size === 0) return noMatch(a.target);
|
|
28634
|
-
const
|
|
28635
|
-
if (
|
|
28636
|
-
const wrong =
|
|
28994
|
+
const entries2 = u.scenarios.map((s) => s.steps[0]).filter((s) => !!s);
|
|
28995
|
+
if (entries2.length === 0) return fail(`${quote(u.name)} has no steps`, `Start "${u.name}" with a step sent by ${what}`);
|
|
28996
|
+
const wrong = entries2.find((s) => !ids.has(s.fromServiceId));
|
|
28637
28997
|
if (wrong) {
|
|
28638
28998
|
return fail(
|
|
28639
28999
|
`${quote(u.name)} starts at ${nodeLabel(run.diagram, wrong.fromServiceId)}, not ${what}: ${stepText(wrong)}`,
|
|
28640
29000
|
`Make the first step of "${u.name}" a step sent by ${what}`
|
|
28641
29001
|
);
|
|
28642
29002
|
}
|
|
28643
|
-
return pass(`${quote(u.name)} starts at ${nodeLabel(run.diagram,
|
|
29003
|
+
return pass(`${quote(u.name)} starts at ${nodeLabel(run.diagram, entries2[0].fromServiceId)}`);
|
|
28644
29004
|
}
|
|
28645
29005
|
function statusMatches(status, wanted) {
|
|
28646
29006
|
if (status === void 0) return false;
|
|
@@ -28843,13 +29203,17 @@ function complete(analysis, pos) {
|
|
|
28843
29203
|
if (tech) {
|
|
28844
29204
|
const closed = line[pos.character] === "]";
|
|
28845
29205
|
const range2 = { start: { line: pos.line, character: pos.character - tech[1].length }, end: pos };
|
|
28846
|
-
return componentCatalog.map((c) =>
|
|
28847
|
-
|
|
28848
|
-
|
|
28849
|
-
|
|
28850
|
-
|
|
28851
|
-
|
|
28852
|
-
|
|
29206
|
+
return componentCatalog.map((c) => {
|
|
29207
|
+
const aliases = "aliases" in c ? c.aliases : [];
|
|
29208
|
+
return {
|
|
29209
|
+
label: c.techStack,
|
|
29210
|
+
kind: "tech",
|
|
29211
|
+
detail: aliases.length ? `${c.category} \xB7 also ${aliases.slice(0, 3).join(", ")}` : c.category,
|
|
29212
|
+
insertText: closed ? c.techStack : `${c.techStack}]`,
|
|
29213
|
+
...aliases.length ? { filterText: [c.techStack, ...aliases].join(" ") } : {},
|
|
29214
|
+
range: range2
|
|
29215
|
+
};
|
|
29216
|
+
});
|
|
28853
29217
|
}
|
|
28854
29218
|
if (inLabel(before2)) return [];
|
|
28855
29219
|
const word = before2.match(/[A-Za-z_]\w*$/)?.[0] ?? "";
|
|
@@ -28990,7 +29354,18 @@ function sections(analysis) {
|
|
|
28990
29354
|
}));
|
|
28991
29355
|
}
|
|
28992
29356
|
var MISSING_CONNECTION = /^No connection between '\w+' and '\w+' in the architecture; add '(\w+) -> (\w+)'$/;
|
|
28993
|
-
function quickFix(analysis, message) {
|
|
29357
|
+
function quickFix(analysis, message, range) {
|
|
29358
|
+
const tech = message.match(DID_YOU_MEAN);
|
|
29359
|
+
if (tech && range) {
|
|
29360
|
+
const line = analysis.lines[range.start.line] ?? "";
|
|
29361
|
+
const open = line.indexOf("[", range.start.character);
|
|
29362
|
+
const close = open < 0 ? -1 : line.indexOf("]", open);
|
|
29363
|
+
if (open < 0 || close < 0) return null;
|
|
29364
|
+
return {
|
|
29365
|
+
title: `Change to '${tech[2]}'`,
|
|
29366
|
+
edit: { range: { start: { line: range.start.line, character: open + 1 }, end: { line: range.start.line, character: close } }, newText: tech[2] }
|
|
29367
|
+
};
|
|
29368
|
+
}
|
|
28994
29369
|
const m = message.match(MISSING_CONNECTION);
|
|
28995
29370
|
if (!m) return null;
|
|
28996
29371
|
const [, from, to] = m;
|
|
@@ -29013,7 +29388,7 @@ function positionAt(text, offset) {
|
|
|
29013
29388
|
|
|
29014
29389
|
// src/server.ts
|
|
29015
29390
|
if (process.argv.includes("--version")) {
|
|
29016
|
-
console.log(true ? "0.
|
|
29391
|
+
console.log(true ? "0.8.0" : "dev");
|
|
29017
29392
|
process.exit(0);
|
|
29018
29393
|
}
|
|
29019
29394
|
var connection = (0, import_node.createConnection)(import_node.ProposedFeatures.all);
|
|
@@ -29054,7 +29429,7 @@ connection.onInitialize(() => ({
|
|
|
29054
29429
|
documentFormattingProvider: true,
|
|
29055
29430
|
codeActionProvider: { codeActionKinds: [import_node.CodeActionKind.QuickFix] }
|
|
29056
29431
|
},
|
|
29057
|
-
serverInfo: { name: "proschi-language-server", version: true ? "0.
|
|
29432
|
+
serverInfo: { name: "proschi-language-server", version: true ? "0.8.0" : "dev" }
|
|
29058
29433
|
}));
|
|
29059
29434
|
function validate(document) {
|
|
29060
29435
|
const analysis = analysisOf(document);
|
|
@@ -29128,6 +29503,7 @@ connection.onCompletion(
|
|
|
29128
29503
|
kind: COMPLETION_KIND[item.kind],
|
|
29129
29504
|
detail: item.detail,
|
|
29130
29505
|
textEdit: { range: item.range, newText: item.snippet ?? item.insertText ?? item.label },
|
|
29506
|
+
...item.filterText ? { filterText: item.filterText } : {},
|
|
29131
29507
|
insertTextFormat: item.snippet ? import_node.InsertTextFormat.Snippet : import_node.InsertTextFormat.PlainText
|
|
29132
29508
|
}))
|
|
29133
29509
|
)
|
|
@@ -29186,7 +29562,7 @@ connection.onCodeAction((params) => {
|
|
|
29186
29562
|
const actions = [];
|
|
29187
29563
|
for (const diagnostic of params.context.diagnostics) {
|
|
29188
29564
|
if (diagnostic.source !== "proschi") continue;
|
|
29189
|
-
const fix = typeof diagnostic.message === "string" && quickFix(analysis, diagnostic.message);
|
|
29565
|
+
const fix = typeof diagnostic.message === "string" && quickFix(analysis, diagnostic.message, diagnostic.range);
|
|
29190
29566
|
if (!fix) continue;
|
|
29191
29567
|
actions.push({
|
|
29192
29568
|
title: fix.title,
|