proschi 0.5.0 → 0.7.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/dist/cli.cjs +637 -236
- package/dist/render.cjs +162 -159
- package/dist/server.cjs +663 -245
- package/grammar/proschi.tmLanguage.json +1 -1
- package/package.json +1 -1
- 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_]/;
|
|
@@ -25577,22 +25737,30 @@ function stripTrailingComment(text) {
|
|
|
25577
25737
|
}
|
|
25578
25738
|
return text;
|
|
25579
25739
|
}
|
|
25580
|
-
|
|
25581
|
-
|
|
25582
|
-
|
|
25583
|
-
|
|
25584
|
-
|
|
25585
|
-
|
|
25586
|
-
|
|
25587
|
-
|
|
25588
|
-
|
|
25740
|
+
var BracketCounter = class {
|
|
25741
|
+
depth = 0;
|
|
25742
|
+
inString = false;
|
|
25743
|
+
escaped = false;
|
|
25744
|
+
/** Adds `text` and returns the depth so far. */
|
|
25745
|
+
feed(text) {
|
|
25746
|
+
for (let i = 0; i < text.length; i++) {
|
|
25747
|
+
const ch = text[i];
|
|
25748
|
+
if (this.escaped) {
|
|
25749
|
+
this.escaped = false;
|
|
25750
|
+
continue;
|
|
25751
|
+
}
|
|
25752
|
+
if (this.inString) {
|
|
25753
|
+
if (ch === "\\") this.escaped = true;
|
|
25754
|
+
else if (ch === '"') this.inString = false;
|
|
25755
|
+
continue;
|
|
25756
|
+
}
|
|
25757
|
+
if (ch === '"') this.inString = true;
|
|
25758
|
+
else if (ch === "{" || ch === "[") this.depth++;
|
|
25759
|
+
else if (ch === "}" || ch === "]") this.depth--;
|
|
25589
25760
|
}
|
|
25590
|
-
|
|
25591
|
-
else if (ch === "{" || ch === "[") depth++;
|
|
25592
|
-
else if (ch === "}" || ch === "]") depth--;
|
|
25761
|
+
return this.depth;
|
|
25593
25762
|
}
|
|
25594
|
-
|
|
25595
|
-
}
|
|
25763
|
+
};
|
|
25596
25764
|
|
|
25597
25765
|
// ../frontend/src/dsl/paths.ts
|
|
25598
25766
|
var PARAM2 = /^\{[^{}/]+\}$/;
|
|
@@ -25630,26 +25798,7 @@ function endpointGroupKey(endpoint, all) {
|
|
|
25630
25798
|
var KINDS = ["client", "edge", "cdn", "loadbalancer", "gateway", "dns", "service", "function", "cache", "database", "search", "analytics", "queue", "storage", "external", "other"];
|
|
25631
25799
|
var DATA_STORE_KINDS = ["cache", "database", "search", "analytics", "queue", "storage"];
|
|
25632
25800
|
var EDGE_KINDS = ["edge", "cdn", "loadbalancer", "gateway", "dns"];
|
|
25633
|
-
var BY_TECH =
|
|
25634
|
-
"AWS CloudFront": "cdn",
|
|
25635
|
-
"Azure CDN": "cdn",
|
|
25636
|
-
"GCP Cloud CDN": "cdn",
|
|
25637
|
-
"Azure Front Door": "cdn",
|
|
25638
|
-
"AWS Load Balancer": "loadbalancer",
|
|
25639
|
-
"GCP Load Balancing": "loadbalancer",
|
|
25640
|
-
"AWS API Gateway": "gateway",
|
|
25641
|
-
"Azure API Management": "gateway",
|
|
25642
|
-
"AWS Route53": "dns",
|
|
25643
|
-
"Azure DNS": "dns",
|
|
25644
|
-
"GCP Cloud DNS": "dns",
|
|
25645
|
-
Redis: "cache",
|
|
25646
|
-
Elasticsearch: "search",
|
|
25647
|
-
"GCP BigQuery": "analytics",
|
|
25648
|
-
InfluxDB: "analytics",
|
|
25649
|
-
TimescaleDB: "analytics",
|
|
25650
|
-
// A note shape annotates the diagram; it is not a client.
|
|
25651
|
-
Note: "other"
|
|
25652
|
-
};
|
|
25801
|
+
var BY_TECH = new Map(componentCatalog.flatMap((c) => "kind" in c ? [[c.techStack, c.kind]] : []));
|
|
25653
25802
|
var BY_TYPE = {
|
|
25654
25803
|
shape: "client",
|
|
25655
25804
|
cdn: "edge",
|
|
@@ -25665,7 +25814,44 @@ var BY_TYPE = {
|
|
|
25665
25814
|
};
|
|
25666
25815
|
function kindOf(node) {
|
|
25667
25816
|
if (node.kind !== "component") return "other";
|
|
25668
|
-
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];
|
|
25669
25855
|
}
|
|
25670
25856
|
function isKind(word) {
|
|
25671
25857
|
return KINDS.includes(word);
|
|
@@ -25775,6 +25961,7 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
|
25775
25961
|
var DEFAULT_TECH = "Rectangle";
|
|
25776
25962
|
var DEFAULT_GROUP_TECH = "Logical Group";
|
|
25777
25963
|
var MAX_SCENARIOS = 32;
|
|
25964
|
+
var MAX_ALT_DEPTH = 16;
|
|
25778
25965
|
var REPLICAS = /^x(\d+)$/;
|
|
25779
25966
|
var PERCENTILES = { p50: 50, p90: 90, p95: 95, p99: 99, p999: 99.9 };
|
|
25780
25967
|
var FIELD_FLAGS = ["key", "index", "unique", "optional"];
|
|
@@ -25786,12 +25973,32 @@ var UNIT_EXAMPLES = {
|
|
|
25786
25973
|
MBps: "a bandwidth, e.g. 500 MB/s or 1 GB/s",
|
|
25787
25974
|
"usd/GB": "a price per gigabyte, e.g. 0.05 usd/GB"
|
|
25788
25975
|
};
|
|
25789
|
-
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";
|
|
25790
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';
|
|
25791
25978
|
var ASSERTION_VERBS = "calls, every scenario calls, never calls, never waits for, writes, responds, starts at, has scenario or handles failure of";
|
|
25792
|
-
var
|
|
25793
|
-
|
|
25794
|
-
|
|
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 '([^']+)'\?/;
|
|
25795
26002
|
function parse(source2, options = {}) {
|
|
25796
26003
|
return new Parser(source2, options).run();
|
|
25797
26004
|
}
|
|
@@ -25802,8 +26009,12 @@ var Parser = class {
|
|
|
25802
26009
|
file;
|
|
25803
26010
|
stack = [];
|
|
25804
26011
|
diagnostics = [];
|
|
26012
|
+
/** Warnings reported so far (see `warning`). */
|
|
26013
|
+
warned = /* @__PURE__ */ new Set();
|
|
25805
26014
|
nodes = /* @__PURE__ */ new Map();
|
|
25806
26015
|
edges = [];
|
|
26016
|
+
/** Connections so far per `from->to`, for the ids of repeated ones. */
|
|
26017
|
+
edgeCount = /* @__PURE__ */ new Map();
|
|
25807
26018
|
useCases = [];
|
|
25808
26019
|
references = [];
|
|
25809
26020
|
imports = [];
|
|
@@ -25880,6 +26091,17 @@ var Parser = class {
|
|
|
25880
26091
|
const loc = (t) => this.locOf(t, line);
|
|
25881
26092
|
const top = this.top();
|
|
25882
26093
|
if (top?.kind === "section") {
|
|
26094
|
+
if (top.skipped !== void 0) {
|
|
26095
|
+
const opens = tokens[tokens.length - 1].kind === "lbrace";
|
|
26096
|
+
if (first.kind !== "rbrace") top.nested = (top.nested ?? 0) + (opens ? 1 : 0);
|
|
26097
|
+
else if (top.nested) top.nested += opens ? 0 : -1;
|
|
26098
|
+
else {
|
|
26099
|
+
this.stack.pop();
|
|
26100
|
+
if (second?.kind === "ident" && second.value === "alt") this.parseAlt(tokens.slice(1), line);
|
|
26101
|
+
else if (second) this.error("Unexpected input after }", loc(second));
|
|
26102
|
+
}
|
|
26103
|
+
return index;
|
|
26104
|
+
}
|
|
25883
26105
|
if (first.kind === "rbrace") {
|
|
25884
26106
|
this.stack.pop();
|
|
25885
26107
|
if (!top.discard) this.closeSection(top);
|
|
@@ -26028,8 +26250,8 @@ var Parser = class {
|
|
|
26028
26250
|
if (tokens[i]?.kind === "string") name = tokens[i++].value;
|
|
26029
26251
|
if (tokens[i]?.kind === "tech") {
|
|
26030
26252
|
const tech = tokens[i++];
|
|
26031
|
-
const resolved =
|
|
26032
|
-
if (resolved
|
|
26253
|
+
const resolved = findTech(tech.value);
|
|
26254
|
+
if (resolved?.type === "group") techStack = resolved.techStack;
|
|
26033
26255
|
else this.warning(`'${tech.value}' is not a group style; using ${DEFAULT_GROUP_TECH}`, this.locOf(tech, line));
|
|
26034
26256
|
}
|
|
26035
26257
|
let position;
|
|
@@ -26104,6 +26326,12 @@ var Parser = class {
|
|
|
26104
26326
|
return;
|
|
26105
26327
|
}
|
|
26106
26328
|
this.expectEnd(tokens, i + 1, line);
|
|
26329
|
+
if (this.stack.filter((f) => f.kind === "alt").length >= MAX_ALT_DEPTH) {
|
|
26330
|
+
const loc2 = this.locOf(keyword, line);
|
|
26331
|
+
this.error(`alt blocks can nest at most ${MAX_ALT_DEPTH} deep; this one is skipped`, loc2);
|
|
26332
|
+
this.stack.push({ kind: "section", section: "test", discard: true, skipped: `alt '${nameToken.value}'`, loc: loc2 });
|
|
26333
|
+
return;
|
|
26334
|
+
}
|
|
26107
26335
|
const owner = top.body;
|
|
26108
26336
|
let set = owner.openAlt;
|
|
26109
26337
|
if (!set) {
|
|
@@ -26156,6 +26384,7 @@ var Parser = class {
|
|
|
26156
26384
|
const resolved = this.resolveTech(t, line);
|
|
26157
26385
|
node.type = resolved.type;
|
|
26158
26386
|
node.techStack = resolved.techStack;
|
|
26387
|
+
if (resolved.inferredKind) node.inferredKind = resolved.inferredKind;
|
|
26159
26388
|
node.kind = resolved.type === "text" ? "text" : resolved.type === "group" ? "group" : "component";
|
|
26160
26389
|
} else if (t.kind === "team") {
|
|
26161
26390
|
node.ownerTeam = t.value;
|
|
@@ -26197,12 +26426,16 @@ var Parser = class {
|
|
|
26197
26426
|
}
|
|
26198
26427
|
let label = labelToken?.value ?? "";
|
|
26199
26428
|
let lastLine = index;
|
|
26200
|
-
|
|
26201
|
-
|
|
26429
|
+
const brackets = new BracketCounter();
|
|
26430
|
+
let depth = brackets.feed(label);
|
|
26431
|
+
if (depth > 0) {
|
|
26432
|
+
while (depth > 0 && lastLine + 1 < this.lines.length) {
|
|
26202
26433
|
lastLine++;
|
|
26203
|
-
|
|
26434
|
+
const more = "\n" + this.lines[lastLine];
|
|
26435
|
+
label += more;
|
|
26436
|
+
depth = brackets.feed(more);
|
|
26204
26437
|
}
|
|
26205
|
-
if (
|
|
26438
|
+
if (depth > 0) this.error("Unclosed { or [ in payload", this.locOf(labelToken, line));
|
|
26206
26439
|
label = label.trimEnd();
|
|
26207
26440
|
}
|
|
26208
26441
|
const loc = {
|
|
@@ -26237,7 +26470,8 @@ var Parser = class {
|
|
|
26237
26470
|
this.error("'-x' marks a failed call and is only allowed in use case steps", this.locOf(arrowToken, line));
|
|
26238
26471
|
} else {
|
|
26239
26472
|
const base = `${from}->${to}`;
|
|
26240
|
-
const count = this.
|
|
26473
|
+
const count = this.edgeCount.get(base) ?? 0;
|
|
26474
|
+
this.edgeCount.set(base, count + 1);
|
|
26241
26475
|
this.edges.push({ id: count ? `${base}#${count + 1}` : base, source: from, target: to, label: unquote(label) || void 0, loc });
|
|
26242
26476
|
}
|
|
26243
26477
|
return lastLine;
|
|
@@ -26505,12 +26739,13 @@ var Parser = class {
|
|
|
26505
26739
|
if (rps === void 0) return;
|
|
26506
26740
|
override.rps = rps;
|
|
26507
26741
|
i++;
|
|
26508
|
-
} 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")) {
|
|
26509
26743
|
part = t.value;
|
|
26510
|
-
const unit = part === "latency" ? "ms" : part === "availability" ? "%" : "usd/month";
|
|
26744
|
+
const unit = part === "latency" || part === "timeout" ? "ms" : part === "availability" ? "%" : "usd/month";
|
|
26511
26745
|
const value = this.quantityOf(tokens[i + 1], unit, t, line);
|
|
26512
26746
|
if (value === void 0) return;
|
|
26513
26747
|
if (part === "latency") override.latencyMs = value;
|
|
26748
|
+
else if (part === "timeout") override.timeoutMs = value;
|
|
26514
26749
|
else if (part === "availability") override.availability = value;
|
|
26515
26750
|
else override.costUsd = value;
|
|
26516
26751
|
i += 2;
|
|
@@ -26918,7 +27153,7 @@ var Parser = class {
|
|
|
26918
27153
|
parseSelectorAtom(tokens, i, line) {
|
|
26919
27154
|
const t = tokens[i];
|
|
26920
27155
|
if (t?.kind === "tech") {
|
|
26921
|
-
const tech =
|
|
27156
|
+
const tech = findTech(t.value)?.techStack ?? t.value;
|
|
26922
27157
|
return { selector: { tech }, next: i + 1, token: t, loc: this.locOf(t, line) };
|
|
26923
27158
|
}
|
|
26924
27159
|
if (isWord(t, "any")) {
|
|
@@ -26943,7 +27178,10 @@ var Parser = class {
|
|
|
26943
27178
|
/** Warns about a selector naming a tech stack that does not exist, and (once every file is read) a node that does not. */
|
|
26944
27179
|
checkSelectorLater({ atoms }) {
|
|
26945
27180
|
for (const { selector, token, loc } of atoms) {
|
|
26946
|
-
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
|
+
}
|
|
26947
27185
|
if ("node" in selector) {
|
|
26948
27186
|
this.deferred.push(() => {
|
|
26949
27187
|
if (!this.nodes.has(selector.node)) this.warning(`Unknown node '${selector.node}'`, loc);
|
|
@@ -27103,11 +27341,27 @@ var Parser = class {
|
|
|
27103
27341
|
where(first, later) {
|
|
27104
27342
|
return first.file === later.file ? `on line ${first.line}` : `in ${fileName(first.file ?? this.options.path ?? "the root document")} on line ${first.line}`;
|
|
27105
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
|
+
*/
|
|
27106
27352
|
resolveTech(token, line) {
|
|
27107
|
-
const
|
|
27108
|
-
if (
|
|
27109
|
-
|
|
27110
|
-
|
|
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 };
|
|
27111
27365
|
}
|
|
27112
27366
|
useCaseId(name) {
|
|
27113
27367
|
const base = slug(name) || "usecase";
|
|
@@ -27163,7 +27417,12 @@ var Parser = class {
|
|
|
27163
27417
|
this.diagnostics.push({ severity: "error", message, ...loc });
|
|
27164
27418
|
}
|
|
27165
27419
|
warning(message, loc) {
|
|
27166
|
-
|
|
27420
|
+
const key = `${loc.file ?? ""}
|
|
27421
|
+
${loc.line}
|
|
27422
|
+
${loc.col}
|
|
27423
|
+
${message}`;
|
|
27424
|
+
if (this.warned.has(key)) return;
|
|
27425
|
+
this.warned.add(key);
|
|
27167
27426
|
this.diagnostics.push({ severity: "warning", message, ...loc });
|
|
27168
27427
|
}
|
|
27169
27428
|
};
|
|
@@ -27196,6 +27455,7 @@ function isWord(token, word) {
|
|
|
27196
27455
|
return token?.kind === "ident" && token.value === word;
|
|
27197
27456
|
}
|
|
27198
27457
|
function sectionName(frame) {
|
|
27458
|
+
if (frame.skipped) return frame.skipped;
|
|
27199
27459
|
if (frame.entity) return `entity '${frame.entity.name}'`;
|
|
27200
27460
|
if (frame.decision) return `decision '${frame.decision.title}'`;
|
|
27201
27461
|
if (frame.test) return `test '${frame.test.name}'`;
|
|
@@ -27311,14 +27571,14 @@ ${text}
|
|
|
27311
27571
|
|
|
27312
27572
|
// ../frontend/src/dsl/format.ts
|
|
27313
27573
|
function format(source2) {
|
|
27314
|
-
const
|
|
27574
|
+
const entries2 = classify(source2.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l));
|
|
27315
27575
|
const out = [];
|
|
27316
27576
|
let run = [];
|
|
27317
27577
|
const flush = () => {
|
|
27318
27578
|
if (run.length) out.push(...renderRun(run));
|
|
27319
27579
|
run = [];
|
|
27320
27580
|
};
|
|
27321
|
-
tidyBlankLines(
|
|
27581
|
+
tidyBlankLines(entries2).forEach((entry) => {
|
|
27322
27582
|
const last = run[run.length - 1];
|
|
27323
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))
|
|
27324
27584
|
flush();
|
|
@@ -27334,19 +27594,19 @@ function format(source2) {
|
|
|
27334
27594
|
var INDENT = " ";
|
|
27335
27595
|
var KEYWORDS = /* @__PURE__ */ new Set(["title", "import", "group", "usecase", "par", "alt"]);
|
|
27336
27596
|
function classify(lines) {
|
|
27337
|
-
const
|
|
27597
|
+
const entries2 = [];
|
|
27338
27598
|
let depth = 0;
|
|
27339
27599
|
let section;
|
|
27340
27600
|
for (let i = 0; i < lines.length; i++) {
|
|
27341
27601
|
const raw = lines[i];
|
|
27342
27602
|
const original = raw.trim();
|
|
27343
27603
|
if (!original) {
|
|
27344
|
-
|
|
27604
|
+
entries2.push({ kind: "blank", depth, text: "" });
|
|
27345
27605
|
continue;
|
|
27346
27606
|
}
|
|
27347
27607
|
const { tokens, diagnostics } = tokenizeLine(raw, i + 1);
|
|
27348
27608
|
if (diagnostics.some((d) => d.severity === "error") || tokens.length === 0) {
|
|
27349
|
-
|
|
27609
|
+
entries2.push({ kind: "line", depth, text: original });
|
|
27350
27610
|
continue;
|
|
27351
27611
|
}
|
|
27352
27612
|
const comment = commentOf(raw, tokens);
|
|
@@ -27355,22 +27615,22 @@ function classify(lines) {
|
|
|
27355
27615
|
const inSection = section !== void 0 && depth === 1 && first.kind !== "rbrace";
|
|
27356
27616
|
const cells = inSection ? sectionCells(section, raw, tokens) : void 0;
|
|
27357
27617
|
if (cells) {
|
|
27358
|
-
|
|
27618
|
+
entries2.push({ ...base, kind: "cells", section, cells });
|
|
27359
27619
|
continue;
|
|
27360
27620
|
}
|
|
27361
27621
|
if (!inSection && first.kind === "ident" && second?.kind === "arrow" && third?.kind === "ident" && tokens.length <= 4 && (!fourth || fourth.kind === "label")) {
|
|
27362
27622
|
const entry = { ...base, kind: "connection", from: first.value, arrow: second.value, to: third.value, label: fourth?.value, continuation: [] };
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
while (
|
|
27623
|
+
const brackets = new BracketCounter();
|
|
27624
|
+
if (fourth && brackets.feed(fourth.value) > 0) {
|
|
27625
|
+
const delta = indent(depth).length - width(leadingSpace(raw));
|
|
27626
|
+
while (i + 1 < lines.length) {
|
|
27367
27627
|
const next = lines[++i];
|
|
27368
|
-
label += "\n" + next;
|
|
27369
27628
|
entry.continuation.push(reindent(next, delta));
|
|
27629
|
+
if (brackets.feed("\n" + next) <= 0) break;
|
|
27370
27630
|
}
|
|
27371
27631
|
while (entry.continuation[entry.continuation.length - 1] === "") entry.continuation.pop();
|
|
27372
27632
|
}
|
|
27373
|
-
|
|
27633
|
+
entries2.push(entry);
|
|
27374
27634
|
continue;
|
|
27375
27635
|
}
|
|
27376
27636
|
const oneLineDecision = first.value === "decision" && second?.kind === "string" && third?.kind === "ident" && third.value === "because";
|
|
@@ -27378,19 +27638,19 @@ function classify(lines) {
|
|
|
27378
27638
|
let k = 1;
|
|
27379
27639
|
const take = (kind) => tokens[k]?.kind === kind ? source(raw, tokens[k++]) : "";
|
|
27380
27640
|
const cells2 = [first.value, take("string"), take("tech"), take("team"), join2(raw, tokens.slice(k))];
|
|
27381
|
-
|
|
27641
|
+
entries2.push({ ...base, kind: "node", cells: cells2 });
|
|
27382
27642
|
continue;
|
|
27383
27643
|
}
|
|
27384
27644
|
const closes = first.kind === "rbrace";
|
|
27385
27645
|
const opens = tokens[tokens.length - 1].kind === "lbrace";
|
|
27386
27646
|
const code = join2(raw, tokens);
|
|
27387
27647
|
const text = withComment(code, comment);
|
|
27388
|
-
|
|
27648
|
+
entries2.push({ kind: "line", depth: Math.max(0, depth - (closes ? 1 : 0)), text: sameTokens(text, tokens) ? text : original, opens, closes });
|
|
27389
27649
|
if (depth === 0) section = sectionOpenedBy(tokens);
|
|
27390
27650
|
for (const t of tokens) depth = Math.max(0, depth + (t.kind === "lbrace" ? 1 : t.kind === "rbrace" ? -1 : 0));
|
|
27391
27651
|
if (depth === 0) section = void 0;
|
|
27392
27652
|
}
|
|
27393
|
-
return
|
|
27653
|
+
return entries2;
|
|
27394
27654
|
}
|
|
27395
27655
|
function sectionOpenedBy(tokens) {
|
|
27396
27656
|
const [first, second] = tokens;
|
|
@@ -27424,9 +27684,9 @@ function sectionCells(section, raw, tokens) {
|
|
|
27424
27684
|
return void 0;
|
|
27425
27685
|
}
|
|
27426
27686
|
}
|
|
27427
|
-
function tidyBlankLines(
|
|
27687
|
+
function tidyBlankLines(entries2) {
|
|
27428
27688
|
const kept = [];
|
|
27429
|
-
for (const entry of
|
|
27689
|
+
for (const entry of entries2) {
|
|
27430
27690
|
if (entry.kind !== "blank") {
|
|
27431
27691
|
if (entry.kind === "line" && entry.closes) while (kept[kept.length - 1]?.kind === "blank") kept.pop();
|
|
27432
27692
|
kept.push(entry);
|
|
@@ -27507,7 +27767,8 @@ var source = (raw, t) => {
|
|
|
27507
27767
|
var withComment = (code, comment) => comment ? code + " " + comment : code;
|
|
27508
27768
|
var leadingSpace = (line) => /^[ \t]*/.exec(line)[0];
|
|
27509
27769
|
var width = (space) => [...space].reduce((n, ch) => n + (ch === " " ? INDENT.length : 1), 0);
|
|
27510
|
-
var
|
|
27770
|
+
var MAX_INDENT = 32;
|
|
27771
|
+
var indent = (depth) => INDENT.repeat(Math.min(depth, MAX_INDENT));
|
|
27511
27772
|
var len = (s) => [...s].length;
|
|
27512
27773
|
var pad = (s, to) => s + " ".repeat(Math.max(0, to - len(s)));
|
|
27513
27774
|
|
|
@@ -27657,6 +27918,8 @@ function accessIn(diagram) {
|
|
|
27657
27918
|
|
|
27658
27919
|
// ../frontend/src/sim/profiles.ts
|
|
27659
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;
|
|
27660
27923
|
var RELATIONAL = {
|
|
27661
27924
|
rps: 2e4,
|
|
27662
27925
|
readRps: 2e4,
|
|
@@ -27670,8 +27933,9 @@ var RELATIONAL = {
|
|
|
27670
27933
|
};
|
|
27671
27934
|
var KIND_PROFILES = {
|
|
27672
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.
|
|
27673
27937
|
edge: { rps: 1e5, latencyMs: 2, availability: 0.9999, costUsd: 50, durable: false },
|
|
27674
|
-
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 },
|
|
27675
27939
|
loadbalancer: { rps: 1e5, latencyMs: 2, availability: 0.9999, costUsd: 50, durable: false },
|
|
27676
27940
|
gateway: { rps: 1e4, latencyMs: 10, availability: 0.9995, costUsd: 100, durable: false },
|
|
27677
27941
|
// Name resolution happens before the request and is cached: off the request path.
|
|
@@ -27683,23 +27947,16 @@ var KIND_PROFILES = {
|
|
|
27683
27947
|
search: { rps: 3e3, latencyMs: 15, availability: 0.999, costUsd: 400, durable: true, consistency: "eventual" },
|
|
27684
27948
|
analytics: { rps: 200, latencyMs: 500, availability: 0.999, costUsd: 300, durable: true, consistency: "eventual" },
|
|
27685
27949
|
queue: { rps: 5e4, latencyMs: 5, availability: 0.9999, costUsd: 200, durable: true, consistency: "strong" },
|
|
27686
|
-
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" },
|
|
27687
27951
|
external: { rps: 1e3, latencyMs: 200, availability: 0.999, costUsd: 0, durable: false },
|
|
27688
27952
|
other: NEUTRAL
|
|
27689
27953
|
};
|
|
27690
27954
|
var NOSQL = { rps: 2e4, latencyMs: 5, availability: 0.9999, costUsd: 500, durable: true, consistency: "eventual" };
|
|
27691
27955
|
var NOSQL_STRONG = { ...NOSQL, consistency: "strong" };
|
|
27692
|
-
var
|
|
27693
|
-
|
|
27694
|
-
"
|
|
27695
|
-
|
|
27696
|
-
CouchDB: NOSQL,
|
|
27697
|
-
"Azure Cosmos DB": NOSQL,
|
|
27698
|
-
MongoDB: NOSQL_STRONG,
|
|
27699
|
-
"GCP Bigtable": NOSQL_STRONG,
|
|
27700
|
-
"GCP Firestore": NOSQL_STRONG,
|
|
27701
|
-
"GCP Spanner": NOSQL_STRONG
|
|
27702
|
-
};
|
|
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
|
+
);
|
|
27703
27960
|
var BANDWIDTH = {
|
|
27704
27961
|
client: 10,
|
|
27705
27962
|
edge: 1e3,
|
|
@@ -27710,9 +27967,10 @@ var BANDWIDTH = {
|
|
|
27710
27967
|
service: 200
|
|
27711
27968
|
};
|
|
27712
27969
|
var DEFAULT_BANDWIDTH_MBPS = 100;
|
|
27970
|
+
var CHARGES_EGRESS = (kind) => kind !== "client" && kind !== "external" && kind !== "dns" && kind !== "other";
|
|
27713
27971
|
function profileOf(node, override) {
|
|
27714
27972
|
const kind = kindOf(node);
|
|
27715
|
-
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];
|
|
27716
27974
|
const baseRead = base.readRps ?? base.rps;
|
|
27717
27975
|
const baseWrite = base.writeRps ?? base.rps;
|
|
27718
27976
|
const readRps = override?.readRps ?? override?.rps ?? baseRead;
|
|
@@ -27731,7 +27989,8 @@ function profileOf(node, override) {
|
|
|
27731
27989
|
durable: override?.durable ?? base.durable,
|
|
27732
27990
|
...consistency && isDataStore(kind) ? { consistency } : {},
|
|
27733
27991
|
bandwidthMBps: override?.bandwidthMBps ?? base.bandwidthMBps ?? BANDWIDTH[kind] ?? DEFAULT_BANDWIDTH_MBPS,
|
|
27734
|
-
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 } : {}
|
|
27735
27994
|
};
|
|
27736
27995
|
}
|
|
27737
27996
|
function capacityOf(p, replicas) {
|
|
@@ -27884,10 +28143,11 @@ function formatUsd(usd) {
|
|
|
27884
28143
|
var DEFAULT_TIMEOUT_MS = 1e3;
|
|
27885
28144
|
var HOT = 0.7;
|
|
27886
28145
|
var QUEUE_CAP = 0.95;
|
|
28146
|
+
var FAILOVER_SHARE = 0.1;
|
|
28147
|
+
var SERVICE_SPREAD = 0.5;
|
|
27887
28148
|
var SECONDS_PER_MONTH = 2592e3;
|
|
27888
28149
|
var BYTES_PER_MB = 1e6;
|
|
27889
28150
|
var BYTES_PER_GB = 1e9;
|
|
27890
|
-
var PERCENTILE_FACTORS = { 50: 1, 90: 1.6, 95: 2, 99: 3, 99.9: 5 };
|
|
27891
28151
|
var PERCENTILE_KEYS = { 50: "p50", 90: "p90", 95: "p95", 99: "p99", 99.9: "p999" };
|
|
27892
28152
|
var PERCENTILES2 = Object.keys(PERCENTILE_KEYS).map(Number);
|
|
27893
28153
|
function components(diagram) {
|
|
@@ -27928,7 +28188,9 @@ function forEachStepRate(diagram, traffic, visit) {
|
|
|
27928
28188
|
const { rps, shares } = traffic.get(u.id) ?? { rps: 0, shares: [] };
|
|
27929
28189
|
u.scenarios.forEach((s, i) => {
|
|
27930
28190
|
const rate = rps * (shares[i] ?? 0);
|
|
27931
|
-
if (rate > 0)
|
|
28191
|
+
if (rate > 0) {
|
|
28192
|
+
for (const step of s.steps) if (!step.failed) visit(step, rate);
|
|
28193
|
+
}
|
|
27932
28194
|
});
|
|
27933
28195
|
}
|
|
27934
28196
|
}
|
|
@@ -27942,35 +28204,76 @@ function computeLoad(diagram, traffic) {
|
|
|
27942
28204
|
});
|
|
27943
28205
|
return load;
|
|
27944
28206
|
}
|
|
27945
|
-
var
|
|
27946
|
-
|
|
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)) {
|
|
27947
28210
|
const gb = /* @__PURE__ */ new Map();
|
|
27948
28211
|
const access = accessIn(diagram);
|
|
27949
28212
|
forEachStepRate(diagram, traffic, (step, rate) => {
|
|
27950
28213
|
if (!step.sizeBytes || step.sizeBytes <= 0) return;
|
|
27951
|
-
const
|
|
27952
|
-
|
|
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);
|
|
27953
28217
|
});
|
|
27954
28218
|
return gb;
|
|
27955
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
|
+
}
|
|
27956
28234
|
function transferMs(step, bandwidthOf) {
|
|
27957
28235
|
if (!step.sizeBytes || step.sizeBytes <= 0) return 0;
|
|
27958
28236
|
const mbps = Math.min(bandwidthOf(step.fromServiceId), bandwidthOf(step.toServiceId));
|
|
27959
|
-
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;
|
|
27960
28238
|
}
|
|
27961
28239
|
var utilizationOf = (load, capacity) => Number.isFinite(capacity) && capacity > 0 ? load / capacity : 0;
|
|
27962
|
-
|
|
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);
|
|
27963
28259
|
var replicatedAvailability = (a, replicas) => 1 - (1 - a) ** replicas;
|
|
27964
|
-
|
|
28260
|
+
var primaryAvailability = (a, replicas) => replicas >= 2 ? 1 - (1 - a) * FAILOVER_SHARE : a;
|
|
28261
|
+
function usageOf(p, replicas, readLoadRps, writeLoadRps, bytesPerSecond = 0) {
|
|
27965
28262
|
const { readRps: readCapacityRps, writeRps: writeCapacityRps } = capacityOf(p, replicas);
|
|
27966
28263
|
const readUtilization = utilizationOf(readLoadRps, readCapacityRps);
|
|
27967
28264
|
const writeUtilization = utilizationOf(writeLoadRps, writeCapacityRps);
|
|
27968
|
-
const
|
|
28265
|
+
const requestUtilization = combinedUtilization(p, readUtilization, writeUtilization);
|
|
27969
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);
|
|
27970
28272
|
return {
|
|
27971
28273
|
loadRps,
|
|
27972
|
-
capacityRps: readCapacityRps === writeCapacityRps ||
|
|
28274
|
+
capacityRps: readCapacityRps === writeCapacityRps || requestUtilization === 0 ? readCapacityRps : loadRps / requestUtilization,
|
|
27973
28275
|
utilization,
|
|
28276
|
+
requestUtilization,
|
|
27974
28277
|
saturated: utilization >= 1,
|
|
27975
28278
|
readLoadRps,
|
|
27976
28279
|
writeLoadRps,
|
|
@@ -27978,27 +28281,68 @@ function usageOf(p, replicas, readLoadRps, writeLoadRps) {
|
|
|
27978
28281
|
writeCapacityRps,
|
|
27979
28282
|
readUtilization,
|
|
27980
28283
|
writeUtilization,
|
|
28284
|
+
bandwidthLoadMBps,
|
|
28285
|
+
bandwidthCapacityMBps,
|
|
28286
|
+
bandwidthUtilization,
|
|
27981
28287
|
writeScaling: p.writeScaling
|
|
27982
28288
|
};
|
|
27983
28289
|
}
|
|
28290
|
+
var bandwidthBound = (n) => n.bandwidthUtilization > n.requestUtilization;
|
|
28291
|
+
var serversOf = (n, replicas) => writeBound(n) ? 1 : replicas;
|
|
27984
28292
|
function usageText(n) {
|
|
27985
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
|
+
}
|
|
27986
28298
|
if (n.writeScaling === "shards" && n.writeLoadRps > 0) {
|
|
27987
28299
|
return `reads ${part(n.readLoadRps, n.readCapacityRps, n.readUtilization)}; writes ${part(n.writeLoadRps, n.writeCapacityRps, n.writeUtilization)}`;
|
|
27988
28300
|
}
|
|
27989
|
-
return part(n.loadRps, n.capacityRps, n.
|
|
28301
|
+
return part(n.loadRps, n.capacityRps, n.requestUtilization);
|
|
27990
28302
|
}
|
|
27991
28303
|
var writeBound = (n) => n.writeScaling === "shards" && n.writeUtilization >= n.readUtilization && n.writeLoadRps > 0;
|
|
27992
|
-
function
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
|
|
27996
|
-
let
|
|
27997
|
-
for (const
|
|
27998
|
-
|
|
27999
|
-
|
|
28000
|
-
|
|
28001
|
-
|
|
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;
|
|
28002
28346
|
}
|
|
28003
28347
|
function mainScenarioIndex(shares) {
|
|
28004
28348
|
let best = 0;
|
|
@@ -28015,20 +28359,27 @@ function profilesOf(diagram, warnings = []) {
|
|
|
28015
28359
|
return new Map(nodes.map((n) => [n.id, profileOf(n, overrides.get(n.id))]));
|
|
28016
28360
|
}
|
|
28017
28361
|
function analyze(diagram, options = {}) {
|
|
28018
|
-
const
|
|
28362
|
+
const defaultTimeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
28019
28363
|
const warnings = [];
|
|
28020
28364
|
const nodes = components(diagram);
|
|
28021
28365
|
const profiles = profilesOf(diagram, warnings);
|
|
28366
|
+
const kindOf2 = (id) => profiles.get(id)?.kind ?? "client";
|
|
28022
28367
|
const traffic = resolveTraffic(diagram, warnings);
|
|
28023
28368
|
const load = computeLoad(diagram, traffic);
|
|
28024
|
-
const egress = computeEgressGb(diagram, traffic);
|
|
28369
|
+
const egress = computeEgressGb(diagram, traffic, kindOf2);
|
|
28370
|
+
const bytes = computeBytesPerSecond(diagram, traffic);
|
|
28371
|
+
const hops = /* @__PURE__ */ new Map();
|
|
28025
28372
|
const nodeResults = nodes.map((n) => {
|
|
28026
28373
|
const p = profiles.get(n.id);
|
|
28027
|
-
const replicas = replicasOf(n);
|
|
28374
|
+
const replicas = Math.max(1, options.replicas?.get(n.id) ?? replicasOf(n));
|
|
28028
28375
|
const { read, write } = load.get(n.id) ?? { read: 0, write: 0 };
|
|
28029
|
-
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);
|
|
28030
28380
|
const egressGbPerMonth = egress.get(n.id) ?? 0;
|
|
28031
28381
|
const egressUsd = egressGbPerMonth * p.egressUsdPerGb;
|
|
28382
|
+
const availability2 = replicatedAvailability(p.availability, replicas);
|
|
28032
28383
|
return {
|
|
28033
28384
|
id: n.id,
|
|
28034
28385
|
kind: p.kind,
|
|
@@ -28036,8 +28387,11 @@ function analyze(diagram, options = {}) {
|
|
|
28036
28387
|
shards: p.shards,
|
|
28037
28388
|
...usage,
|
|
28038
28389
|
...p.consistency ? { consistency: p.consistency } : {},
|
|
28039
|
-
|
|
28040
|
-
|
|
28390
|
+
servers,
|
|
28391
|
+
waitProbability: hop.waitProbability,
|
|
28392
|
+
latencyMs: hopMean(hop),
|
|
28393
|
+
availability: availability2,
|
|
28394
|
+
writeAvailability: p.writeScaling === "shards" ? primaryAvailability(p.availability, replicas) : availability2,
|
|
28041
28395
|
costUsd: replicas * p.shards * p.costUsd + egressUsd,
|
|
28042
28396
|
egressGbPerMonth,
|
|
28043
28397
|
egressUsd,
|
|
@@ -28045,28 +28399,49 @@ function analyze(diagram, options = {}) {
|
|
|
28045
28399
|
};
|
|
28046
28400
|
});
|
|
28047
28401
|
const nodeById = new Map(nodeResults.map((n) => [n.id, n]));
|
|
28048
|
-
const hopMs = (id) => nodeById.get(id)?.latencyMs ?? 0;
|
|
28049
|
-
const availabilityOf = (id) => nodeById.get(id)?.availability ?? 1;
|
|
28050
28402
|
const bandwidthOf = (id) => profiles.get(id)?.bandwidthMBps ?? Infinity;
|
|
28403
|
+
const timeoutOf = (id) => profiles.get(id)?.timeoutMs ?? defaultTimeoutMs;
|
|
28051
28404
|
for (const n of nodeResults) {
|
|
28052
28405
|
const usage = usageText(n);
|
|
28053
28406
|
if (n.saturated) warnings.push(`'${n.id}' is saturated: ${usage}. ${writeBound(n) ? "Add shards for writes" : "Add replicas"} or take load off it.`);
|
|
28054
28407
|
else if (n.utilization > HOT) warnings.push(`'${n.id}' runs hot: ${usage}.`);
|
|
28055
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
|
+
});
|
|
28056
28419
|
const useCases = diagram.useCases.map((u) => {
|
|
28057
28420
|
const { rps, shares } = traffic.get(u.id);
|
|
28058
|
-
const
|
|
28059
|
-
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
28065
|
-
|
|
28066
|
-
|
|
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
|
+
})
|
|
28067
28437
|
);
|
|
28068
28438
|
if (rps > 0 && u.scenarios.length === 0) warnings.push(`"${u.name}" has traffic but no steps`);
|
|
28069
|
-
|
|
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) };
|
|
28070
28445
|
});
|
|
28071
28446
|
return {
|
|
28072
28447
|
nodes: nodeResults,
|
|
@@ -28077,8 +28452,10 @@ function analyze(diagram, options = {}) {
|
|
|
28077
28452
|
warnings
|
|
28078
28453
|
};
|
|
28079
28454
|
}
|
|
28080
|
-
function
|
|
28081
|
-
|
|
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));
|
|
28082
28459
|
}
|
|
28083
28460
|
function useCaseAvailability(useCase, shares, availabilityOf) {
|
|
28084
28461
|
if (useCase.scenarios.length === 0) return 1;
|
|
@@ -28105,9 +28482,9 @@ function singlePointsOfFailure(diagram) {
|
|
|
28105
28482
|
// ../frontend/src/sim/tests.ts
|
|
28106
28483
|
var pass = (message) => ({ passed: true, message });
|
|
28107
28484
|
var fail = (message, hint) => ({ passed: false, message, hint });
|
|
28108
|
-
function
|
|
28485
|
+
function makeRun(diagram, analysis) {
|
|
28109
28486
|
const access = accessIn(diagram);
|
|
28110
|
-
|
|
28487
|
+
return {
|
|
28111
28488
|
diagram,
|
|
28112
28489
|
analysis,
|
|
28113
28490
|
nodes: new Map(analysis.nodes.map((n) => [n.id, n])),
|
|
@@ -28116,6 +28493,9 @@ function runTests(diagram, analysis = analyze(diagram)) {
|
|
|
28116
28493
|
profiles: profilesOf(diagram),
|
|
28117
28494
|
isWrite: (step) => access(step) === "write"
|
|
28118
28495
|
};
|
|
28496
|
+
}
|
|
28497
|
+
function runTests(diagram, analysis = analyze(diagram)) {
|
|
28498
|
+
const run = makeRun(diagram, analysis);
|
|
28119
28499
|
const results = (diagram.requirements ?? []).map((r, i) => {
|
|
28120
28500
|
const { passed, message, hint } = requirement(run, r);
|
|
28121
28501
|
return { id: `req:${i + 1}`, name: requirementName(r), category: CATEGORY[r.kind], passed, message, hint, loc: r.loc };
|
|
@@ -28207,7 +28587,7 @@ function latency(run, u, q, maxMs) {
|
|
|
28207
28587
|
const value = `${key} of ${u.name} is ${formatMs(ms)}${result.rps > 0 ? "" : " with no traffic"}`;
|
|
28208
28588
|
const message = `${value} (limit ${formatMs(maxMs)})`;
|
|
28209
28589
|
if (ms < maxMs) return { ...pass(message), value };
|
|
28210
|
-
const index = Math.
|
|
28590
|
+
const index = Math.min(result.tailScenario[key] ?? 0, u.scenarios.length - 1);
|
|
28211
28591
|
const lead = u.scenarios.length > 1 ? `The "${u.scenarios[index].name}" path (${formatPercent(result.scenarios[index].share)} of traffic) sets ${key}. ` : "";
|
|
28212
28592
|
return { ...fail(message, lead + latencyHint(run, u, u.scenarios[index])), value };
|
|
28213
28593
|
}
|
|
@@ -28226,6 +28606,10 @@ function scenarioLatency(run, useCase, scenario, q, maxMs) {
|
|
|
28226
28606
|
}
|
|
28227
28607
|
function saturationHint(run, n) {
|
|
28228
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
|
+
}
|
|
28229
28613
|
if (writeBound(n)) {
|
|
28230
28614
|
const perShard = n.writeCapacityRps / n.shards;
|
|
28231
28615
|
const shards = Math.ceil(n.writeLoadRps / (perShard * HOT));
|
|
@@ -28237,11 +28621,12 @@ function saturationHint(run, n) {
|
|
|
28237
28621
|
}
|
|
28238
28622
|
function latencyHint(run, u, scenario) {
|
|
28239
28623
|
const hops = criticalPath(u, scenario).flat();
|
|
28240
|
-
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;
|
|
28241
28626
|
const slowest = hops.reduce((best, h) => !best || cost2(h) > cost2(best) ? h : best, void 0);
|
|
28242
28627
|
if (!slowest) return "Shorten the synchronous path of the request.";
|
|
28243
28628
|
const label = nodeLabel(run.diagram, slowest.target);
|
|
28244
|
-
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 }).`;
|
|
28245
28630
|
const sized = hops.find((h) => (h.step.sizeBytes ?? 0) > 0);
|
|
28246
28631
|
const node = run.nodes.get(slowest.target);
|
|
28247
28632
|
if (node && node.utilization > HOT) return `${label} runs at ${formatPercent(node.utilization)} and queues requests; add replicas to it.`;
|
|
@@ -28255,8 +28640,11 @@ function availability(run, u, minPercent) {
|
|
|
28255
28640
|
if (result.availability * 100 >= minPercent - 1e-9) return { ...pass(message), value };
|
|
28256
28641
|
const shares = run.shares.get(u.id) ?? [];
|
|
28257
28642
|
const main = u.scenarios.length ? pathNodes(u, u.scenarios[mainScenarioIndex(shares)]) : [];
|
|
28258
|
-
const
|
|
28259
|
-
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`;
|
|
28260
28648
|
return { ...fail(message, hint), value };
|
|
28261
28649
|
}
|
|
28262
28650
|
function writeGap(run, u, s, accept) {
|
|
@@ -28314,20 +28702,34 @@ function survive(run, target) {
|
|
|
28314
28702
|
if (selected.length === 0) {
|
|
28315
28703
|
return target === "any" ? pass("No nodes to lose") : fail(`No node matches ${what}`, "Use a node id, [Tech] or any <kind>");
|
|
28316
28704
|
}
|
|
28705
|
+
const latencies = (run.diagram.requirements ?? []).filter((r) => r.kind === "latency" && requirement(run, r).passed);
|
|
28317
28706
|
const failures = [];
|
|
28318
28707
|
for (const node of selected) {
|
|
28319
28708
|
const n = run.nodes.get(node.id);
|
|
28320
28709
|
const label = nodeLabel(run.diagram, node.id);
|
|
28321
28710
|
const replicas = replicasOf(node);
|
|
28322
28711
|
if (replicas >= 2) {
|
|
28323
|
-
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`;
|
|
28324
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)}`;
|
|
28325
28717
|
failures.push(
|
|
28326
28718
|
fail(
|
|
28327
|
-
`Losing
|
|
28719
|
+
`Losing ${lost} ${leaves}${bandwidthBound(left) ? "" : ` (${formatPercent(left.utilization)})`}`,
|
|
28328
28720
|
writeBound(left) ? saturationHint(run, { ...n, ...left }) : `Add a replica to ${label} (x${replicas + 1}) so the rest carry the load`
|
|
28329
28721
|
)
|
|
28330
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
|
+
);
|
|
28331
28733
|
}
|
|
28332
28734
|
continue;
|
|
28333
28735
|
}
|
|
@@ -28589,16 +28991,16 @@ function startsAt(run, a) {
|
|
|
28589
28991
|
const ids = idsOf(run, a.target);
|
|
28590
28992
|
const what = selectorText(a.target);
|
|
28591
28993
|
if (ids.size === 0) return noMatch(a.target);
|
|
28592
|
-
const
|
|
28593
|
-
if (
|
|
28594
|
-
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));
|
|
28595
28997
|
if (wrong) {
|
|
28596
28998
|
return fail(
|
|
28597
28999
|
`${quote(u.name)} starts at ${nodeLabel(run.diagram, wrong.fromServiceId)}, not ${what}: ${stepText(wrong)}`,
|
|
28598
29000
|
`Make the first step of "${u.name}" a step sent by ${what}`
|
|
28599
29001
|
);
|
|
28600
29002
|
}
|
|
28601
|
-
return pass(`${quote(u.name)} starts at ${nodeLabel(run.diagram,
|
|
29003
|
+
return pass(`${quote(u.name)} starts at ${nodeLabel(run.diagram, entries2[0].fromServiceId)}`);
|
|
28602
29004
|
}
|
|
28603
29005
|
function statusMatches(status, wanted) {
|
|
28604
29006
|
if (status === void 0) return false;
|
|
@@ -28801,13 +29203,17 @@ function complete(analysis, pos) {
|
|
|
28801
29203
|
if (tech) {
|
|
28802
29204
|
const closed = line[pos.character] === "]";
|
|
28803
29205
|
const range2 = { start: { line: pos.line, character: pos.character - tech[1].length }, end: pos };
|
|
28804
|
-
return componentCatalog.map((c) =>
|
|
28805
|
-
|
|
28806
|
-
|
|
28807
|
-
|
|
28808
|
-
|
|
28809
|
-
|
|
28810
|
-
|
|
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
|
+
});
|
|
28811
29217
|
}
|
|
28812
29218
|
if (inLabel(before2)) return [];
|
|
28813
29219
|
const word = before2.match(/[A-Za-z_]\w*$/)?.[0] ?? "";
|
|
@@ -28948,7 +29354,18 @@ function sections(analysis) {
|
|
|
28948
29354
|
}));
|
|
28949
29355
|
}
|
|
28950
29356
|
var MISSING_CONNECTION = /^No connection between '\w+' and '\w+' in the architecture; add '(\w+) -> (\w+)'$/;
|
|
28951
|
-
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
|
+
}
|
|
28952
29369
|
const m = message.match(MISSING_CONNECTION);
|
|
28953
29370
|
if (!m) return null;
|
|
28954
29371
|
const [, from, to] = m;
|
|
@@ -28971,7 +29388,7 @@ function positionAt(text, offset) {
|
|
|
28971
29388
|
|
|
28972
29389
|
// src/server.ts
|
|
28973
29390
|
if (process.argv.includes("--version")) {
|
|
28974
|
-
console.log(true ? "0.
|
|
29391
|
+
console.log(true ? "0.7.0" : "dev");
|
|
28975
29392
|
process.exit(0);
|
|
28976
29393
|
}
|
|
28977
29394
|
var connection = (0, import_node.createConnection)(import_node.ProposedFeatures.all);
|
|
@@ -29012,7 +29429,7 @@ connection.onInitialize(() => ({
|
|
|
29012
29429
|
documentFormattingProvider: true,
|
|
29013
29430
|
codeActionProvider: { codeActionKinds: [import_node.CodeActionKind.QuickFix] }
|
|
29014
29431
|
},
|
|
29015
|
-
serverInfo: { name: "proschi-language-server", version: true ? "0.
|
|
29432
|
+
serverInfo: { name: "proschi-language-server", version: true ? "0.7.0" : "dev" }
|
|
29016
29433
|
}));
|
|
29017
29434
|
function validate(document) {
|
|
29018
29435
|
const analysis = analysisOf(document);
|
|
@@ -29086,6 +29503,7 @@ connection.onCompletion(
|
|
|
29086
29503
|
kind: COMPLETION_KIND[item.kind],
|
|
29087
29504
|
detail: item.detail,
|
|
29088
29505
|
textEdit: { range: item.range, newText: item.snippet ?? item.insertText ?? item.label },
|
|
29506
|
+
...item.filterText ? { filterText: item.filterText } : {},
|
|
29089
29507
|
insertTextFormat: item.snippet ? import_node.InsertTextFormat.Snippet : import_node.InsertTextFormat.PlainText
|
|
29090
29508
|
}))
|
|
29091
29509
|
)
|
|
@@ -29144,7 +29562,7 @@ connection.onCodeAction((params) => {
|
|
|
29144
29562
|
const actions = [];
|
|
29145
29563
|
for (const diagnostic of params.context.diagnostics) {
|
|
29146
29564
|
if (diagnostic.source !== "proschi") continue;
|
|
29147
|
-
const fix = typeof diagnostic.message === "string" && quickFix(analysis, diagnostic.message);
|
|
29565
|
+
const fix = typeof diagnostic.message === "string" && quickFix(analysis, diagnostic.message, diagnostic.range);
|
|
29148
29566
|
if (!fix) continue;
|
|
29149
29567
|
actions.push({
|
|
29150
29568
|
title: fix.title,
|