feat: add examples of Rust and TypeScript (#53)

* feat: demonstrate rust client/server

* feat: demonstrate TypeScript node client

* feat: add typescript server

* refactor: remove non-functional cc toolchain

Rust depends on having one that works. We can find a more surgical way to prevent protoc cc_binary from getting built.

* chore: missing file

* refactor: move to tools
diff --git a/.gitignore b/.gitignore
index 630e0fa..bc80243 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,6 @@
 .bazelrc.user
 .idea/
 .ijwb/
+node_modules/
 # Ignore until it is more stable
 MODULE.bazel.lock
diff --git a/.prettierignore b/.prettierignore
index 2e117bf..57efff1 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1 +1,2 @@
 docs/*.md
+examples/**/*_pb.d.ts
diff --git a/examples/.bazelignore b/examples/.bazelignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/examples/.bazelignore
@@ -0,0 +1 @@
+node_modules
diff --git a/examples/.bazelrc b/examples/.bazelrc
index 06d1d1b..6c89af4 100644
--- a/examples/.bazelrc
+++ b/examples/.bazelrc
@@ -4,3 +4,5 @@
 
 # Force rules_go to disable CGO even though we have a (fake) C++ toolchain registered.
 common --host_platform=//tools:no_cgo_host_platform
+common --@aspect_rules_ts//ts:skipLibCheck=always
+common --@aspect_rules_ts//ts:default_to_tsc_transpiler
diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel
index 5eda38d..20bd431 100644
--- a/examples/BUILD.bazel
+++ b/examples/BUILD.bazel
@@ -1,30 +1,19 @@
-load("@rules_go//proto:def.bzl", "go_proto_library")
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@protobuf//bazel:py_proto_library.bzl", "py_proto_library")
+load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin")
+load("@aspect_rules_ts//ts:defs.bzl", "ts_config")
+load("@npm//:defs.bzl", "npm_link_all_packages")
 
 package(default_visibility = ["//visibility:public"])
 
-proto_library(
-    name = "foo_proto",
-    srcs = ["foo.proto"],
-    deps = ["@com_google_protobuf//:any_proto"],
+copy_to_bin(
+    name = "package_json",
+    srcs = ["package.json"],
 )
 
-py_proto_library(
-    name = "foo_py_proto",
-    deps = [":foo_proto"],
-)
+# Link all direct dependencies in /package.json to
+# bazel-bin/node_modules
+npm_link_all_packages(name = "node_modules")
 
-# Broken by https://github.com/protocolbuffers/protobuf/pull/19679
-# which causes building C++ code from source.
-# TODO: re-enable once protobuf honors the toolchain
-# java_proto_library(
-#     name = "foo_java_proto",
-#     deps = [":foo_proto"],
-# )
-
-go_proto_library(
-    name = "foo_go_proto",
-    importpath = "example.com/foo_proto",
-    proto = ":foo_proto",
+ts_config(
+    name = "tsconfig",
+    src = "tsconfig.json",
 )
diff --git a/examples/MODULE.bazel b/examples/MODULE.bazel
index a7180ec..c70cebd 100644
--- a/examples/MODULE.bazel
+++ b/examples/MODULE.bazel
@@ -1,11 +1,15 @@
 bazel_dep(name = "toolchains_protoc", version = "0.0.0")
 bazel_dep(name = "aspect_bazel_lib", version = "2.11.0")
+bazel_dep(name = "aspect_rules_js", version = "2.2.0")
 bazel_dep(name = "aspect_rules_py", version = "1.3.2")
+bazel_dep(name = "aspect_rules_ts", version = "3.5.1")
 bazel_dep(name = "platforms", version = "0.0.11")
 bazel_dep(name = "protobuf", version = "29.3")
 bazel_dep(name = "rules_java", version = "8.6.3")
 bazel_dep(name = "rules_proto", version = "7.1.0")
 bazel_dep(name = "rules_python", version = "1.2.0-rc0")
+bazel_dep(name = "rules_rust", version = "0.59.1")
+bazel_dep(name = "rules_rust_prost", version = "0.59.1")
 bazel_dep(name = "rules_go", version = "0.53.0")
 bazel_dep(name = "rules_uv", version = "0.56.0")
 
@@ -72,3 +76,97 @@
     sha256 = "0532ad1024d62361561acaedb974d7d16889e7670b36e23e9321dd6b9d334ef9",
     urls = ["https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/4.27.0-RC3/protobuf-java-4.27.0-RC3.jar"],
 )
+
+####### RUST ##########
+RUST_EDITION = "2021"
+
+RUST_VERSION = "1.79.0"
+
+rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
+rust.toolchain(
+    edition = RUST_EDITION,
+    versions = [RUST_VERSION],
+)
+use_repo(rust, "rust_toolchains")
+
+register_toolchains("@rust_toolchains//:all")
+
+# Proto toolchain
+
+crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate")
+
+# protobuf / gRPC dependencies
+crate.spec(
+    package = "prost",
+    version = "0.13.1",
+)
+crate.spec(
+    default_features = False,
+    package = "prost-types",
+    version = "0.13.1",
+)
+crate.spec(
+    features = ["transport"],
+    package = "tonic",
+    version = "0.12.1",
+)
+crate.spec(
+    package = "tonic-build",
+    version = "0.12.1",
+)
+crate.spec(
+    package = "protoc-gen-prost",
+    version = "0.4.0",
+)
+crate.annotation(
+    crate = "protoc-gen-prost",
+    gen_binaries = ["protoc-gen-prost"],
+)
+crate.spec(
+    package = "protoc-gen-tonic",
+    version = "0.4.0",
+)
+crate.annotation(
+    crate = "protoc-gen-tonic",
+    gen_binaries = ["protoc-gen-tonic"],
+)
+crate.spec(
+    default_features = False,
+    features = [
+        "macros",
+        "net",
+        "rt-multi-thread",
+        "signal",
+    ],
+    package = "tokio",
+    version = "1.38",
+)
+crate.from_specs()
+use_repo(crate, "crates")
+
+####### TYPESCRIPT ##########
+npm = use_extension(
+    "@aspect_rules_js//npm:extensions.bzl",
+    "npm",
+    dev_dependency = True,
+)
+
+pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm")
+
+npm.npm_translate_lock(
+    name = "npm",
+    pnpm_lock = "//:pnpm-lock.yaml",
+)
+use_repo(npm, "npm")
+
+use_repo(pnpm, "pnpm")
+
+rules_ts_ext = use_extension(
+    "@aspect_rules_ts//ts:extensions.bzl",
+    "ext",
+    dev_dependency = True,
+)
+rules_ts_ext.deps(
+    ts_version_from = "//:package.json",
+)
+use_repo(rules_ts_ext, "npm_typescript")
diff --git a/examples/WORKSPACE.bazel b/examples/WORKSPACE.bazel
deleted file mode 100644
index e69de29..0000000
--- a/examples/WORKSPACE.bazel
+++ /dev/null
diff --git a/examples/foo.proto b/examples/foo.proto
deleted file mode 100644
index 465255b..0000000
--- a/examples/foo.proto
+++ /dev/null
@@ -1,11 +0,0 @@
-syntax = "proto3";
-
-import "google/protobuf/any.proto";
-
-option go_package = "example.com/foo_proto";
-option java_package = "proto";
-
-message Foo {
-    string msg = 1;
-    repeated google.protobuf.Any details = 2;
-}
diff --git a/examples/go/BUILD b/examples/go/BUILD
index db2b107..05689b6 100644
--- a/examples/go/BUILD
+++ b/examples/go/BUILD
@@ -1,7 +1,7 @@
 load("@rules_go//go:def.bzl", "go_test")
 
 go_test(
-    name = "foo_proto_test",
-    srcs = ["foo_proto_test.go"],
-    deps = ["//:foo_go_proto"],
+    name = "greeter_proto_test",
+    srcs = ["greeter_proto_test.go"],
+    deps = ["//proto:greeter_go_proto"],
 )
diff --git a/examples/go/foo_proto_test.go b/examples/go/foo_proto_test.go
deleted file mode 100644
index 114188a..0000000
--- a/examples/go/foo_proto_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package proto_test
-
-import (
-	"testing"
-
-	"example.com/foo_proto"
-)
-
-func TestFoo(t *testing.T) {
-	msg := &foo_proto.Foo{
-		Msg: "hello world",
-	}
-	if msg.Msg != "hello world" {
-		t.Fail()
-	}
-}
diff --git a/examples/go/greeter_proto_test.go b/examples/go/greeter_proto_test.go
new file mode 100644
index 0000000..691d6c0
--- /dev/null
+++ b/examples/go/greeter_proto_test.go
@@ -0,0 +1,16 @@
+package proto_test
+
+import (
+	"testing"
+
+	"example.com/greeter_proto"
+)
+
+func TestFoo(t *testing.T) {
+	msg := &greeter_proto.HelloReply{
+		Message: "hello world",
+	}
+	if msg.Message != "hello world" {
+		t.Fail()
+	}
+}
diff --git a/examples/package.json b/examples/package.json
new file mode 100644
index 0000000..3597763
--- /dev/null
+++ b/examples/package.json
@@ -0,0 +1,15 @@
+{
+  "dependencies": {
+    "@bufbuild/protobuf": "^2.2.4",
+    "@connectrpc/connect-node": "2.0.2",
+    "@connectrpc/connect-fastify": "~2.0.2",
+    "fastify": "~5.2.1"
+  },
+  "devDependencies": {
+    "@bufbuild/protoc-gen-es": "~2.2.4",
+    "@connectrpc/connect": "~2.0.2",
+    "typescript": "5.7.2",
+    "@types/node": "~22.13.10"
+  },
+  "type": "module"
+}
diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml
new file mode 100644
index 0000000..f2b58ab
--- /dev/null
+++ b/examples/pnpm-lock.yaml
@@ -0,0 +1,606 @@
+lockfileVersion: "6.0"
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+dependencies:
+  "@bufbuild/protobuf":
+    specifier: ^2.2.4
+    version: 2.2.4
+  "@connectrpc/connect-fastify":
+    specifier: ~2.0.2
+    version: 2.0.2(@bufbuild/protobuf@2.2.4)(@connectrpc/connect-node@2.0.2)(@connectrpc/connect@2.0.2)(fastify@5.2.1)
+  "@connectrpc/connect-node":
+    specifier: 2.0.2
+    version: 2.0.2(@bufbuild/protobuf@2.2.4)(@connectrpc/connect@2.0.2)
+  fastify:
+    specifier: ~5.2.1
+    version: 5.2.1
+
+devDependencies:
+  "@bufbuild/protoc-gen-es":
+    specifier: ~2.2.4
+    version: 2.2.4(@bufbuild/protobuf@2.2.4)
+  "@connectrpc/connect":
+    specifier: ~2.0.2
+    version: 2.0.2(@bufbuild/protobuf@2.2.4)
+  "@types/node":
+    specifier: ~22.13.10
+    version: 22.13.10
+  typescript:
+    specifier: 5.7.2
+    version: 5.7.2
+
+packages:
+  /@bufbuild/protobuf@2.2.4:
+    resolution:
+      {
+        integrity: sha512-P9xQgtMh71TA7tHTnbDe68zcI+TPnkyyfBIhGaUr4iUEIXN7yI01DyjmmdEwXTk5OlISBJYkoxCVj2dwmHqIkA==,
+      }
+
+  /@bufbuild/protoc-gen-es@2.2.4(@bufbuild/protobuf@2.2.4):
+    resolution:
+      {
+        integrity: sha512-s8hCpjYBCIKTFGfoEdnxIxgZkp3tBtFLz96J82TKNtrOATR5VBj3EPp53/rAG7+I7uguPfcDslO0EZxlzLVtdA==,
+      }
+    engines: { node: ">=14" }
+    hasBin: true
+    peerDependencies:
+      "@bufbuild/protobuf": 2.2.4
+    peerDependenciesMeta:
+      "@bufbuild/protobuf":
+        optional: true
+    dependencies:
+      "@bufbuild/protobuf": 2.2.4
+      "@bufbuild/protoplugin": 2.2.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@bufbuild/protoplugin@2.2.4:
+    resolution:
+      {
+        integrity: sha512-OGoc31DokbTu6Ev8808oWPPMgBKLdEBTheMYvRK//2jI7E7oSMxPBlFeRTRiRUfhbQ2TvW20SgJwTLDUsNe8Iw==,
+      }
+    dependencies:
+      "@bufbuild/protobuf": 2.2.4
+      "@typescript/vfs": 1.6.1(typescript@5.4.5)
+      typescript: 5.4.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@connectrpc/connect-fastify@2.0.2(@bufbuild/protobuf@2.2.4)(@connectrpc/connect-node@2.0.2)(@connectrpc/connect@2.0.2)(fastify@5.2.1):
+    resolution:
+      {
+        integrity: sha512-rOobWfWc09+Ws4x+tfZFd12UBezMInq+V5chqbgGuGgajZXyEPRfu8ePo0IiKltWptOq7oPAyaEbadpgcZsnBg==,
+      }
+    engines: { node: ">=18.14.1" }
+    peerDependencies:
+      "@bufbuild/protobuf": ^2.2.0
+      "@connectrpc/connect": 2.0.2
+      "@connectrpc/connect-node": 2.0.2
+      fastify: ^4.22.1 || ^5.1.0
+    dependencies:
+      "@bufbuild/protobuf": 2.2.4
+      "@connectrpc/connect": 2.0.2(@bufbuild/protobuf@2.2.4)
+      "@connectrpc/connect-node": 2.0.2(@bufbuild/protobuf@2.2.4)(@connectrpc/connect@2.0.2)
+      fastify: 5.2.1
+    dev: false
+
+  /@connectrpc/connect-node@2.0.2(@bufbuild/protobuf@2.2.4)(@connectrpc/connect@2.0.2):
+    resolution:
+      {
+        integrity: sha512-33Ut3SRkb6SugpwVCtXXRvUrOdtiyG6z6d5+eijBOLOI75sw1tDCwcs0o/9WL3rUj1M08dLUrPmJB47fjpv6EA==,
+      }
+    engines: { node: ">=18.14.1" }
+    peerDependencies:
+      "@bufbuild/protobuf": ^2.2.0
+      "@connectrpc/connect": 2.0.2
+    dependencies:
+      "@bufbuild/protobuf": 2.2.4
+      "@connectrpc/connect": 2.0.2(@bufbuild/protobuf@2.2.4)
+    dev: false
+
+  /@connectrpc/connect@2.0.2(@bufbuild/protobuf@2.2.4):
+    resolution:
+      {
+        integrity: sha512-xZuylIUNvNlH52e/4eQsZvY4QZyDJRtEFEDnn/yBrv5Xi5ZZI/p8X+GAHH35ucVaBvv9u7OzHZo8+tEh1EFTxA==,
+      }
+    peerDependencies:
+      "@bufbuild/protobuf": ^2.2.0
+    dependencies:
+      "@bufbuild/protobuf": 2.2.4
+
+  /@fastify/ajv-compiler@4.0.2:
+    resolution:
+      {
+        integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==,
+      }
+    dependencies:
+      ajv: 8.17.1
+      ajv-formats: 3.0.1(ajv@8.17.1)
+      fast-uri: 3.0.6
+    dev: false
+
+  /@fastify/error@4.1.0:
+    resolution:
+      {
+        integrity: sha512-KeFcciOr1eo/YvIXHP65S94jfEEqn1RxTRBT1aJaHxY5FK0/GDXYozsQMMWlZoHgi8i0s+YtrLsgj/JkUUjSkQ==,
+      }
+    dev: false
+
+  /@fastify/fast-json-stringify-compiler@5.0.2:
+    resolution:
+      {
+        integrity: sha512-YdR7gqlLg1xZAQa+SX4sMNzQHY5pC54fu9oC5aYSUqBhyn6fkLkrdtKlpVdCNPlwuUuXA1PjFTEmvMF6ZVXVGw==,
+      }
+    dependencies:
+      fast-json-stringify: 6.0.1
+    dev: false
+
+  /@fastify/forwarded@3.0.0:
+    resolution:
+      {
+        integrity: sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==,
+      }
+    dev: false
+
+  /@fastify/merge-json-schemas@0.2.1:
+    resolution:
+      {
+        integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==,
+      }
+    dependencies:
+      dequal: 2.0.3
+    dev: false
+
+  /@fastify/proxy-addr@5.0.0:
+    resolution:
+      {
+        integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==,
+      }
+    dependencies:
+      "@fastify/forwarded": 3.0.0
+      ipaddr.js: 2.2.0
+    dev: false
+
+  /@types/node@22.13.10:
+    resolution:
+      {
+        integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==,
+      }
+    dependencies:
+      undici-types: 6.20.0
+    dev: true
+
+  /@typescript/vfs@1.6.1(typescript@5.4.5):
+    resolution:
+      {
+        integrity: sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA==,
+      }
+    peerDependencies:
+      typescript: "*"
+    dependencies:
+      debug: 4.4.0
+      typescript: 5.4.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /abstract-logging@2.0.1:
+    resolution:
+      {
+        integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==,
+      }
+    dev: false
+
+  /ajv-formats@3.0.1(ajv@8.17.1):
+    resolution:
+      {
+        integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==,
+      }
+    peerDependencies:
+      ajv: ^8.0.0
+    peerDependenciesMeta:
+      ajv:
+        optional: true
+    dependencies:
+      ajv: 8.17.1
+    dev: false
+
+  /ajv@8.17.1:
+    resolution:
+      {
+        integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==,
+      }
+    dependencies:
+      fast-deep-equal: 3.1.3
+      fast-uri: 3.0.6
+      json-schema-traverse: 1.0.0
+      require-from-string: 2.0.2
+    dev: false
+
+  /atomic-sleep@1.0.0:
+    resolution:
+      {
+        integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==,
+      }
+    engines: { node: ">=8.0.0" }
+    dev: false
+
+  /avvio@9.1.0:
+    resolution:
+      {
+        integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==,
+      }
+    dependencies:
+      "@fastify/error": 4.1.0
+      fastq: 1.19.1
+    dev: false
+
+  /cookie@1.0.2:
+    resolution:
+      {
+        integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==,
+      }
+    engines: { node: ">=18" }
+    dev: false
+
+  /debug@4.4.0:
+    resolution:
+      {
+        integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==,
+      }
+    engines: { node: ">=6.0" }
+    peerDependencies:
+      supports-color: "*"
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+    dependencies:
+      ms: 2.1.3
+    dev: true
+
+  /dequal@2.0.3:
+    resolution:
+      {
+        integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==,
+      }
+    engines: { node: ">=6" }
+    dev: false
+
+  /fast-decode-uri-component@1.0.1:
+    resolution:
+      {
+        integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==,
+      }
+    dev: false
+
+  /fast-deep-equal@3.1.3:
+    resolution:
+      {
+        integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==,
+      }
+    dev: false
+
+  /fast-json-stringify@6.0.1:
+    resolution:
+      {
+        integrity: sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==,
+      }
+    dependencies:
+      "@fastify/merge-json-schemas": 0.2.1
+      ajv: 8.17.1
+      ajv-formats: 3.0.1(ajv@8.17.1)
+      fast-uri: 3.0.6
+      json-schema-ref-resolver: 2.0.1
+      rfdc: 1.4.1
+    dev: false
+
+  /fast-querystring@1.1.2:
+    resolution:
+      {
+        integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==,
+      }
+    dependencies:
+      fast-decode-uri-component: 1.0.1
+    dev: false
+
+  /fast-redact@3.5.0:
+    resolution:
+      {
+        integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==,
+      }
+    engines: { node: ">=6" }
+    dev: false
+
+  /fast-uri@3.0.6:
+    resolution:
+      {
+        integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==,
+      }
+    dev: false
+
+  /fastify@5.2.1:
+    resolution:
+      {
+        integrity: sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==,
+      }
+    dependencies:
+      "@fastify/ajv-compiler": 4.0.2
+      "@fastify/error": 4.1.0
+      "@fastify/fast-json-stringify-compiler": 5.0.2
+      "@fastify/proxy-addr": 5.0.0
+      abstract-logging: 2.0.1
+      avvio: 9.1.0
+      fast-json-stringify: 6.0.1
+      find-my-way: 9.2.0
+      light-my-request: 6.6.0
+      pino: 9.6.0
+      process-warning: 4.0.1
+      rfdc: 1.4.1
+      secure-json-parse: 3.0.2
+      semver: 7.7.1
+      toad-cache: 3.7.0
+    dev: false
+
+  /fastq@1.19.1:
+    resolution:
+      {
+        integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==,
+      }
+    dependencies:
+      reusify: 1.1.0
+    dev: false
+
+  /find-my-way@9.2.0:
+    resolution:
+      {
+        integrity: sha512-d3uCir8Hmg7W1Ywp8nKf2lJJYU9Nwinvo+1D39Dn09nz65UKXIxUh7j7K8zeWhxqe1WrkS7FJyON/Q/3lPoc6w==,
+      }
+    engines: { node: ">=14" }
+    dependencies:
+      fast-deep-equal: 3.1.3
+      fast-querystring: 1.1.2
+      safe-regex2: 4.0.1
+    dev: false
+
+  /ipaddr.js@2.2.0:
+    resolution:
+      {
+        integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==,
+      }
+    engines: { node: ">= 10" }
+    dev: false
+
+  /json-schema-ref-resolver@2.0.1:
+    resolution:
+      {
+        integrity: sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==,
+      }
+    dependencies:
+      dequal: 2.0.3
+    dev: false
+
+  /json-schema-traverse@1.0.0:
+    resolution:
+      {
+        integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==,
+      }
+    dev: false
+
+  /light-my-request@6.6.0:
+    resolution:
+      {
+        integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==,
+      }
+    dependencies:
+      cookie: 1.0.2
+      process-warning: 4.0.1
+      set-cookie-parser: 2.7.1
+    dev: false
+
+  /ms@2.1.3:
+    resolution:
+      {
+        integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==,
+      }
+    dev: true
+
+  /on-exit-leak-free@2.1.2:
+    resolution:
+      {
+        integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==,
+      }
+    engines: { node: ">=14.0.0" }
+    dev: false
+
+  /pino-abstract-transport@2.0.0:
+    resolution:
+      {
+        integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==,
+      }
+    dependencies:
+      split2: 4.2.0
+    dev: false
+
+  /pino-std-serializers@7.0.0:
+    resolution:
+      {
+        integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==,
+      }
+    dev: false
+
+  /pino@9.6.0:
+    resolution:
+      {
+        integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==,
+      }
+    hasBin: true
+    dependencies:
+      atomic-sleep: 1.0.0
+      fast-redact: 3.5.0
+      on-exit-leak-free: 2.1.2
+      pino-abstract-transport: 2.0.0
+      pino-std-serializers: 7.0.0
+      process-warning: 4.0.1
+      quick-format-unescaped: 4.0.4
+      real-require: 0.2.0
+      safe-stable-stringify: 2.5.0
+      sonic-boom: 4.2.0
+      thread-stream: 3.1.0
+    dev: false
+
+  /process-warning@4.0.1:
+    resolution:
+      {
+        integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==,
+      }
+    dev: false
+
+  /quick-format-unescaped@4.0.4:
+    resolution:
+      {
+        integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==,
+      }
+    dev: false
+
+  /real-require@0.2.0:
+    resolution:
+      {
+        integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==,
+      }
+    engines: { node: ">= 12.13.0" }
+    dev: false
+
+  /require-from-string@2.0.2:
+    resolution:
+      {
+        integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==,
+      }
+    engines: { node: ">=0.10.0" }
+    dev: false
+
+  /ret@0.5.0:
+    resolution:
+      {
+        integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==,
+      }
+    engines: { node: ">=10" }
+    dev: false
+
+  /reusify@1.1.0:
+    resolution:
+      {
+        integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==,
+      }
+    engines: { iojs: ">=1.0.0", node: ">=0.10.0" }
+    dev: false
+
+  /rfdc@1.4.1:
+    resolution:
+      {
+        integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==,
+      }
+    dev: false
+
+  /safe-regex2@4.0.1:
+    resolution:
+      {
+        integrity: sha512-goqsB+bSlOmVX+CiFX2PFc1OV88j5jvBqIM+DgqrucHnUguAUNtiNOs+aTadq2NqsLQ+TQ3UEVG3gtSFcdlkCg==,
+      }
+    dependencies:
+      ret: 0.5.0
+    dev: false
+
+  /safe-stable-stringify@2.5.0:
+    resolution:
+      {
+        integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==,
+      }
+    engines: { node: ">=10" }
+    dev: false
+
+  /secure-json-parse@3.0.2:
+    resolution:
+      {
+        integrity: sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==,
+      }
+    dev: false
+
+  /semver@7.7.1:
+    resolution:
+      {
+        integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==,
+      }
+    engines: { node: ">=10" }
+    hasBin: true
+    dev: false
+
+  /set-cookie-parser@2.7.1:
+    resolution:
+      {
+        integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==,
+      }
+    dev: false
+
+  /sonic-boom@4.2.0:
+    resolution:
+      {
+        integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==,
+      }
+    dependencies:
+      atomic-sleep: 1.0.0
+    dev: false
+
+  /split2@4.2.0:
+    resolution:
+      {
+        integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==,
+      }
+    engines: { node: ">= 10.x" }
+    dev: false
+
+  /thread-stream@3.1.0:
+    resolution:
+      {
+        integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==,
+      }
+    dependencies:
+      real-require: 0.2.0
+    dev: false
+
+  /toad-cache@3.7.0:
+    resolution:
+      {
+        integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==,
+      }
+    engines: { node: ">=12" }
+    dev: false
+
+  /typescript@5.4.5:
+    resolution:
+      {
+        integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==,
+      }
+    engines: { node: ">=14.17" }
+    hasBin: true
+    dev: true
+
+  /typescript@5.7.2:
+    resolution:
+      {
+        integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==,
+      }
+    engines: { node: ">=14.17" }
+    hasBin: true
+    dev: true
+
+  /undici-types@6.20.0:
+    resolution:
+      {
+        integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==,
+      }
+    dev: true
diff --git a/examples/proto/BUILD.bazel b/examples/proto/BUILD.bazel
new file mode 100644
index 0000000..88c206c
--- /dev/null
+++ b/examples/proto/BUILD.bazel
@@ -0,0 +1,48 @@
+load("@aspect_rules_ts//ts:proto.bzl", "ts_proto_library")
+load("@rules_go//proto:def.bzl", "go_proto_library")
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@rules_rust_prost//:defs.bzl", "rust_prost_library")
+load("@protobuf//bazel:py_proto_library.bzl", "py_proto_library")
+
+package(default_visibility = ["//visibility:public"])
+
+proto_library(
+    name = "greeter_proto",
+    srcs = ["greeter.proto"],
+    deps = ["@com_google_protobuf//:any_proto"],
+)
+
+py_proto_library(
+    name = "greeter_py_proto",
+    deps = [":greeter_proto"],
+)
+
+# Broken by https://github.com/protocolbuffers/protobuf/pull/19679
+# which causes building C++ code from source.
+# TODO: re-enable once protobuf honors the toolchain
+# java_proto_library(
+#     name = "greeter_java_proto",
+#     deps = [":greeter_proto"],
+# )
+
+go_proto_library(
+    name = "greeter_go_proto",
+    importpath = "example.com/greeter_proto",
+    proto = ":greeter_proto",
+)
+
+# Generate Rust bindings from the generated proto files
+# https://bazelbuild.github.io/rules_rust/rust_proto.html#rust_prost_library
+rust_prost_library(
+    name = "greeter_rust_proto",
+    proto = ":greeter_proto",
+    visibility = ["//visibility:public"],
+)
+
+ts_proto_library(
+    name = "greeter_ts_proto",
+    gen_connect_es = False,
+    node_modules = "//:node_modules",
+    proto = ":greeter_proto",
+    proto_srcs = ["greeter.proto"],  # to copy greeter.d.ts back to the source tree
+)
diff --git a/examples/proto/greeter.proto b/examples/proto/greeter.proto
new file mode 100644
index 0000000..c2c6d1c
--- /dev/null
+++ b/examples/proto/greeter.proto
@@ -0,0 +1,25 @@
+syntax = "proto3";
+
+// https://github.com/hyperium/tonic/blob/master/examples/proto/helloworld/helloworld.proto
+package proto;
+option go_package = "example.com/greeter_proto";
+option java_package = "proto";
+
+import "google/protobuf/any.proto";
+
+// The greeting service definition.
+service Greeter {
+    // Sends a greeting
+    rpc SayHello(HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+    string name = 1;
+    repeated google.protobuf.Any details = 2;
+}
+
+// The response message containing the greetings
+message HelloReply {
+    string message = 1;
+}
diff --git a/examples/proto/greeter_pb.d.ts b/examples/proto/greeter_pb.d.ts
new file mode 100644
index 0000000..67922cd
--- /dev/null
+++ b/examples/proto/greeter_pb.d.ts
@@ -0,0 +1,64 @@
+// @generated by protoc-gen-es v2.2.4 with parameter "keep_empty_files=true,target=js+dts"
+// @generated from file proto/greeter.proto (package proto, syntax proto3)
+/* eslint-disable */
+
+import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1";
+import type { Message } from "@bufbuild/protobuf";
+import type { Any } from "@bufbuild/protobuf/wkt";
+
+/**
+ * Describes the file proto/greeter.proto.
+ */
+export declare const file_proto_greeter: GenFile;
+
+/**
+ * @generated from message proto.HelloRequest
+ */
+export declare type HelloRequest = Message<"proto.HelloRequest"> & {
+  /**
+   * @generated from field: string name = 1;
+   */
+  name: string;
+
+  /**
+   * @generated from field: repeated google.protobuf.Any details = 2;
+   */
+  details: Any[];
+};
+
+/**
+ * Describes the message proto.HelloRequest.
+ * Use `create(HelloRequestSchema)` to create a new message.
+ */
+export declare const HelloRequestSchema: GenMessage<HelloRequest>;
+
+/**
+ * @generated from message proto.HelloReply
+ */
+export declare type HelloReply = Message<"proto.HelloReply"> & {
+  /**
+   * @generated from field: string message = 1;
+   */
+  message: string;
+};
+
+/**
+ * Describes the message proto.HelloReply.
+ * Use `create(HelloReplySchema)` to create a new message.
+ */
+export declare const HelloReplySchema: GenMessage<HelloReply>;
+
+/**
+ * @generated from service proto.Greeter
+ */
+export declare const Greeter: GenService<{
+  /**
+   * @generated from rpc proto.Greeter.SayHello
+   */
+  sayHello: {
+    methodKind: "unary";
+    input: typeof HelloRequestSchema;
+    output: typeof HelloReplySchema;
+  },
+}>;
+
diff --git a/examples/proto/src/lib.rs b/examples/proto/src/lib.rs
new file mode 100644
index 0000000..860d77e
--- /dev/null
+++ b/examples/proto/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod proto {
+    tonic::include_proto!("proto");
+}
diff --git a/examples/python/BUILD b/examples/python/BUILD
index 9529879..34becda 100644
--- a/examples/python/BUILD
+++ b/examples/python/BUILD
@@ -3,5 +3,5 @@
 py_test(
     name = "message_test",
     srcs = ["message_test.py"],
-    deps = ["//:foo_py_proto"],
+    deps = ["//proto:greeter_py_proto"],
 )
diff --git a/examples/python/message_test.py b/examples/python/message_test.py
index 0ea0782..2e03b51 100644
--- a/examples/python/message_test.py
+++ b/examples/python/message_test.py
@@ -1,12 +1,12 @@
 import sys
 import unittest
 
-from _main import foo_pb2
+from proto import greeter_pb2
 
 class TestCase(unittest.TestCase):
     def test_message(self):
-        got = foo_pb2.Foo(
-            msg = "hello world",
+        got = greeter_pb2.HelloReply(
+            message = "hello world",
         )
         self.assertIsNotNone(got)
 
diff --git a/examples/rust/client/BUILD.bazel b/examples/rust/client/BUILD.bazel
new file mode 100644
index 0000000..08a422c
--- /dev/null
+++ b/examples/rust/client/BUILD.bazel
@@ -0,0 +1,23 @@
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+
+# Build binary
+# https://bazelbuild.github.io/rules_rust/defs.html#rust_binary
+rust_binary(
+    name = "client",
+    srcs = glob([
+        "src/*.rs",
+    ]),
+    crate_root = "src/main.rs",
+    rustc_flags = [
+        "-Copt-level=0",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        # Internal crates
+        "//proto:greeter_rust_proto",
+        # External crates
+        "@crates//:prost-types",
+        "@crates//:tokio",
+        "@crates//:tonic",
+    ],
+)
diff --git a/examples/rust/client/src/main.rs b/examples/rust/client/src/main.rs
new file mode 100644
index 0000000..f4949aa
--- /dev/null
+++ b/examples/rust/client/src/main.rs
@@ -0,0 +1,29 @@
+use greeter_proto::proto::greeter_client::GreeterClient;
+use greeter_proto::proto::HelloRequest;
+use greeter_proto::any_proto::google::protobuf::Any;
+
+// https://github.com/hyperium/tonic/blob/master/examples/src/helloworld/client.rs
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let mut client = GreeterClient::connect("http://[::1]:5042")
+        .await
+        .expect("[Client]: Failed to connect to server.");
+
+    let detail = Any {
+        type_url: "type.googleapis.com/mypackage.MyMessage".to_string(),
+        value: b"details".to_vec(),
+    };
+    let request = tonic::Request::new(HelloRequest {
+        name: "Rust Client".into(),
+        details: vec![detail],
+    });
+
+    let response = client
+        .say_hello(request)
+        .await
+        .expect("[Client]: Failed to get a response from the server");
+
+    println!("RESPONSE={:?}", response);
+
+    Ok(())
+}
diff --git a/examples/rust/server/BUILD.bazel b/examples/rust/server/BUILD.bazel
new file mode 100644
index 0000000..f273a90
--- /dev/null
+++ b/examples/rust/server/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+
+# Build binary
+# https://bazelbuild.github.io/rules_rust/defs.html#rust_binary
+rust_binary(
+    name = "server",
+    srcs = glob([
+        "src/*.rs",
+    ]),
+    crate_root = "src/main.rs",
+    rustc_flags = ["-Copt-level=0"],
+    visibility = ["//visibility:public"],
+    deps = [
+        # Internal crates
+        "//proto:greeter_rust_proto",
+        # External crates
+        "@crates//:tokio",
+        "@crates//:tonic",
+    ],
+)
diff --git a/examples/rust/server/src/main.rs b/examples/rust/server/src/main.rs
new file mode 100644
index 0000000..7d22bb8
--- /dev/null
+++ b/examples/rust/server/src/main.rs
@@ -0,0 +1,40 @@
+use std::error::Error;
+
+use tonic::transport::Server;
+
+use greeter_proto::proto::greeter_server::GreeterServer;
+
+use crate::server::MyGreeter;
+
+mod server;
+mod shutdown_utils;
+
+// https://github.com/hyperium/tonic/blob/master/examples/src/helloworld/server.rs
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn Error>> {
+    let addr = "[::1]:5042"
+        .parse()
+        .expect("[Server]: Failed to parse socket address");
+
+    let grpc_svc = GreeterServer::new(MyGreeter::new());
+
+    // Shutdown signal handler
+    let signal = shutdown_utils::signal_handler("gRPC Greeter server");
+
+    let grpc_server = Server::builder()
+        .add_service(grpc_svc)
+        .serve_with_shutdown(addr, signal);
+
+    let grpc_handle = tokio::spawn(grpc_server);
+
+    println!("GreeterServer listening on {}", addr);
+    match tokio::try_join!(grpc_handle) {
+        Ok(_) => {}
+        Err(e) => {
+            println!("[Server]: Error: Failed to start gRPC Greeter server.");
+            println!("[Server]: Error: {:?}", e);
+        }
+    }
+
+    Ok(())
+}
\ No newline at end of file
diff --git a/examples/rust/server/src/server.rs b/examples/rust/server/src/server.rs
new file mode 100644
index 0000000..4984721
--- /dev/null
+++ b/examples/rust/server/src/server.rs
@@ -0,0 +1,28 @@
+use tonic::{Request, Response, Status};
+
+use greeter_proto::proto::greeter_server::Greeter;
+use greeter_proto::proto::{HelloReply, HelloRequest};
+
+#[derive(Copy, Clone)]
+pub struct MyGreeter {}
+
+impl MyGreeter {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+#[tonic::async_trait]
+impl Greeter for MyGreeter {
+    async fn say_hello(
+        &self,
+        request: Request<HelloRequest>,
+    ) -> Result<Response<HelloReply>, Status> {
+        println!("Got a request from {:?}", request.remote_addr());
+
+        let reply = HelloReply {
+            message: format!("Hello {}!", request.into_inner().name),
+        };
+        Ok(Response::new(reply))
+    }
+}
diff --git a/examples/rust/server/src/shutdown_utils.rs b/examples/rust/server/src/shutdown_utils.rs
new file mode 100644
index 0000000..20d60e1
--- /dev/null
+++ b/examples/rust/server/src/shutdown_utils.rs
@@ -0,0 +1,54 @@
+//
+/// Registers a signal handler that waits for a signal that indicates a shutdown request.
+// https://stackoverflow.com/questions/77585473/rust-tokio-how-to-handle-more-signals-than-just-sigint-i-e-sigquit?noredirect=1#comment136778587_77585473
+pub async fn signal_handler(svc: &str) {
+    wait_for_signal_impl(svc).await
+}
+
+/// Waits for a signal that requests a graceful shutdown. Supports the following signals on unix:
+/// * SIGTERM
+/// * SIGINT (Ctrl-C)
+/// * SIGQUIT
+/// * SIGHUP
+#[cfg(unix)]
+async fn wait_for_signal_impl(svc: &str) {
+    use tokio::signal::unix::{signal, SignalKind};
+
+    // Docs: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html
+    let mut signal_terminate = signal(SignalKind::terminate()).unwrap();
+    let mut signal_interrupt = signal(SignalKind::interrupt()).unwrap();
+    let mut signal_quit = signal(SignalKind::quit()).unwrap();
+    let mut signal_hang = signal(SignalKind::hangup()).unwrap();
+
+    // https://docs.rs/tokio/latest/tokio/macro.select.html
+    tokio::select! {
+        _ = signal_terminate.recv() => println!("* {svc} received SIGTERM"),
+        _ = signal_interrupt.recv() => println!("* {svc} received SIGINT"),
+        _ = signal_quit.recv() => println!("* {svc} received SIGQUIT"),
+        _ = signal_hang.recv() => println!(" * {svc} received SIGHUP"),
+    }
+}
+
+/// Waits for a signal that requests a graceful shutdown. Supports the following signals on Windows:
+/// * ctrl_c
+/// * ctrl_break
+/// * ctrl_close
+/// * ctrl_shutdown
+#[cfg(windows)]
+async fn wait_for_signal_impl(svc: &str) {
+    use tokio::signal::windows;
+
+    // Docs: https://learn.microsoft.com/en-us/windows/console/handlerroutine
+    let mut signal_c = windows::ctrl_c().unwrap();
+    let mut signal_break = windows::ctrl_break().unwrap();
+    let mut signal_close = windows::ctrl_close().unwrap();
+    let mut signal_shutdown = windows::ctrl_shutdown().unwrap();
+
+    // https://docs.rs/tokio/latest/tokio/macro.select.html
+    tokio::select! {
+        _ = signal_c.recv() => println!("* {svc} received CTRL_C."),
+        _ = signal_break.recv() => println!("* {svc} received CTRL_BREAK."),
+        _ = signal_close.recv() => println!("* {svc} received CTRL_CLOSE."),
+        _ = signal_shutdown.recv() => println!("* {svc} received CTRL_SHUTDOWN."),
+    }
+}
diff --git a/examples/tools/BUILD.bazel b/examples/tools/BUILD.bazel
index 8d490c0..a891879 100644
--- a/examples/tools/BUILD.bazel
+++ b/examples/tools/BUILD.bazel
@@ -1,6 +1,24 @@
 load("@platforms//host:constraints.bzl", "HOST_CONSTRAINTS")
+load("@rules_rust//rust:defs.bzl", "rust_library_group")
 load("@rules_uv//uv:pip.bzl", "pip_compile")
 
+package(default_visibility = ["//tools/toolchains:__pkg__"])
+
+rust_library_group(
+    name = "prost_runtime",
+    deps = [
+        "@crates//:prost",
+    ],
+)
+
+rust_library_group(
+    name = "tonic_runtime",
+    deps = [
+        ":prost_runtime",
+        "@crates//:tonic",
+    ],
+)
+
 pip_compile(
     name = "generate_requirements_txt",
     requirements_in = "requirements.in",
diff --git a/examples/tools/toolchains/BUILD.bazel b/examples/tools/toolchains/BUILD.bazel
index 1286682..ccb0c72 100644
--- a/examples/tools/toolchains/BUILD.bazel
+++ b/examples/tools/toolchains/BUILD.bazel
@@ -1,12 +1,5 @@
-"""Define a non-functional cc toolchain.
-
-To fail-fast in cases where we are forced to compile third-party C++ code,
-define a cc toolchain that doesn't work, by using 'false' as the compiler.
-See https://bazel.build/tutorials/ccp-toolchain-config
-"""
-
 load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain")
-load("defs.bzl", "cc_toolchain_config")
+load("@rules_rust_prost//:defs.bzl", "rust_prost_toolchain")
 
 # Configure protoc to have the right arguments for generating Python stubs.
 # NB: the protobuf team intends to remove --python_out and instead use a protoc plugin for Python stub emit.
@@ -27,29 +20,17 @@
     toolchain_type = "@rules_java//java/proto:toolchain_type",
 )
 
-################
-# Setup a non-functional C++ toolchain, so we're assured that no C++ compilation
-# will be expected for engineers working in our repo.
-# That's the critical guarantee of toolchains_protoc
-filegroup(name = "empty")
-
-cc_toolchain_config(name = "noop_toolchain_config")
-
-cc_toolchain(
-    name = "noop_toolchain",
-    all_files = ":empty",
-    compiler_files = ":empty",
-    dwp_files = ":empty",
-    linker_files = ":empty",
-    objcopy_files = ":empty",
-    strip_files = ":empty",
-    supports_param_files = 0,
-    toolchain_config = ":noop_toolchain_config",
-    toolchain_identifier = "noop-toolchain",
+rust_prost_toolchain(
+    name = "prost_toolchain_impl",
+    prost_plugin = "@crates//:protoc-gen-prost__protoc-gen-prost",
+    prost_runtime = "//tools:prost_runtime",
+    prost_types = "@crates//:prost-types",
+    tonic_plugin = "@crates//:protoc-gen-tonic__protoc-gen-tonic",
+    tonic_runtime = "//tools:tonic_runtime",
 )
 
 toolchain(
-    name = "cc_toolchain",
-    toolchain = ":noop_toolchain",
-    toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+    name = "prost_toolchain",
+    toolchain = "prost_toolchain_impl",
+    toolchain_type = "@rules_rust_prost//:toolchain_type",
 )
diff --git a/examples/tools/toolchains/defs.bzl b/examples/tools/toolchains/defs.bzl
deleted file mode 100644
index 4aeee59..0000000
--- a/examples/tools/toolchains/defs.bzl
+++ /dev/null
@@ -1,20 +0,0 @@
-"Configure a cc toolchain to call 'false' if used."
-
-def _impl(ctx):
-    return cc_common.create_cc_toolchain_config_info(
-        ctx = ctx,
-        toolchain_identifier = "noop-toolchain",
-        host_system_name = "local",
-        target_system_name = "local",
-        target_cpu = "k8",
-        target_libc = "unknown",
-        compiler = "false",
-        abi_version = "unknown",
-        abi_libc_version = "unknown",
-    )
-
-cc_toolchain_config = rule(
-    implementation = _impl,
-    attrs = {},
-    provides = [CcToolchainConfigInfo],
-)
diff --git a/examples/tsconfig.json b/examples/tsconfig.json
new file mode 100644
index 0000000..d5cb4df
--- /dev/null
+++ b/examples/tsconfig.json
@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "lib": ["ES2018"],
+    "module": "ES2022",
+    "target": "ES2017",
+    "moduleResolution": "node"
+  }
+}
diff --git a/examples/typescript/client/BUILD.bazel b/examples/typescript/client/BUILD.bazel
new file mode 100644
index 0000000..464ce97
--- /dev/null
+++ b/examples/typescript/client/BUILD.bazel
@@ -0,0 +1,21 @@
+load("@aspect_rules_js//js:defs.bzl", "js_binary")
+load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
+
+ts_project(
+    name = "main",
+    srcs = ["main.mts"],
+    tsconfig = "//:tsconfig",
+    deps = [
+        "//:node_modules/@connectrpc/connect",
+        "//:node_modules/@connectrpc/connect-node",
+        "//:node_modules/@types/node",
+        "//:package_json",
+        "//proto:greeter_ts_proto",
+    ],
+)
+
+js_binary(
+    name = "client",
+    data = [":main"],
+    entry_point = "main.mjs",
+)
diff --git a/examples/typescript/client/main.mts b/examples/typescript/client/main.mts
new file mode 100644
index 0000000..4c8613c
--- /dev/null
+++ b/examples/typescript/client/main.mts
@@ -0,0 +1,21 @@
+import { createClient , CallOptions} from "@connectrpc/connect";
+import { createGrpcTransport } from "@connectrpc/connect-node";
+import {Greeter} from "../../proto/greeter_pb.js";
+
+const transport = createGrpcTransport({
+  baseUrl: "http://localhost:5042",
+});
+
+async function main() {
+  const client = createClient(Greeter, transport);
+  const res = await client.sayHello({
+    name: "TypeScript Client",
+  }, {onHeader: (headers) => {
+    console.log("Headers:")
+    for (const [name, value] of headers) {
+      console.log(`${name}: ${value}`);
+    }
+  }});
+  console.log((res as /*todo*/any).message);
+}
+void main();
diff --git a/examples/typescript/server/BUILD.bazel b/examples/typescript/server/BUILD.bazel
new file mode 100644
index 0000000..99559a5
--- /dev/null
+++ b/examples/typescript/server/BUILD.bazel
@@ -0,0 +1,25 @@
+load("@aspect_rules_js//js:defs.bzl", "js_binary")
+load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
+
+ts_project(
+    name = "main",
+    srcs = [
+        "connect.mts",
+        "main.mts",
+    ],
+    tsconfig = "//:tsconfig",
+    deps = [
+        "//:node_modules/@connectrpc/connect",
+        "//:node_modules/@connectrpc/connect-fastify",
+        "//:node_modules/@types/node",
+        "//:node_modules/fastify",
+        "//:package_json",
+        "//proto:greeter_ts_proto",
+    ],
+)
+
+js_binary(
+    name = "server",
+    data = [":main"],
+    entry_point = "main.mjs",
+)
diff --git a/examples/typescript/server/connect.mts b/examples/typescript/server/connect.mts
new file mode 100644
index 0000000..23e63eb
--- /dev/null
+++ b/examples/typescript/server/connect.mts
@@ -0,0 +1,11 @@
+import { ConnectRouter, HandlerContext } from "@connectrpc/connect";
+import { Greeter, HelloRequest } from "../../proto/greeter_pb.js";
+
+export default (router: ConnectRouter) =>
+  router.service(Greeter, {
+    async sayHello(req: HelloRequest, context: HandlerContext) {
+      return {
+        message: `Hello ${req.name}!`,
+      };
+    }
+  });
diff --git a/examples/typescript/server/main.mts b/examples/typescript/server/main.mts
new file mode 100644
index 0000000..ecf9c0f
--- /dev/null
+++ b/examples/typescript/server/main.mts
@@ -0,0 +1,19 @@
+import { fastify } from "fastify";
+import { fastifyConnectPlugin } from "@connectrpc/connect-fastify";
+import routes from "./connect.mjs";
+import { readFileSync } from "fs";
+
+// See https://connectrpc.com/docs/node/getting-started
+async function main() {
+  const server = fastify({http2: true});
+  await server.register(fastifyConnectPlugin, {routes});
+  server.get("/", (_, reply) => {
+    reply.type("text/plain");
+    reply.send("Hello World!");
+  });
+  await server.listen({ host: "localhost", port: 5042 });
+  console.log("server is listening at", server.addresses());
+}
+// You can remove the main() wrapper if you set type: module in your package.json,
+// and update your tsconfig.json with target: es2017 and module: es2022.
+void main();