Sometimes one backend needs an answer from another before it can continue. A service edge describes that relationship: which runtime is calling, which runtime receives the call, and what the caller names that connection.
Fluffy resolves the destination address and invocation identity. Your programs still define the HTTP method, path, request body, and response behavior.
#Understand a service edge
The edge name belongs to the caller. For example, records-api can identify a connection from worker to the api runtime in application example-app. Its generated client is named after the edge, not after an arbitrary URL supplied by the application.
Adding the edge does not make every runtime a caller. Another runtime needs its own uses.services entry and generated package. This is the same per-runtime model shown in How access works.
#Service calls and messages have different completion points
A service call returns an HTTP response from the destination. A message publish returns when the messaging service accepts the message, before the consumer necessarily runs. Use the response or asynchronous workflow that your application actually needs; changing transport does not remove failure handling.
A timeout does not prove the destination performed no work. Design retry-sensitive operations with a stable request identity or another appropriate idempotency mechanism.
#The caller's identity is not the end user's identity
Private invocation authenticates the calling runtime. If the operation is performed on behalf of a person, the destination still needs an application-defined way to authorize that operation. Fluffy's GCP service adapter uses the authorization header for runtime invocation; it is not a transparent channel for an arbitrary browser bearer token.
#Configure a private call
#Declare a service edge
Add this to an existing worker in fluffy-chainsaw.yaml. Here the application is named example-app and contains an api runtime:
runtimes:
worker:
uses:
services:
- name: records-api
application: example-app
runtime: api
actions: [invoke]Keep the destination's declared port and actual handler. For another application, set its declared application/runtime names and make the destination available to the selected workspace/deployment. Do not substitute a public URL for those identities.
#Call the API from your runtime
Generate the worker's SDK for your application's name:
fluffy-chainsaw generate --application example-app --runtime worker --language typescriptUse the sidecar setup for your selected language. These examples use example-app and module example.com/app; adjust the imports to your application. TypeScript uses address and transport; Python uses channel. Go passes its connection and a deadline-bearing context to the function:
import * as runtimev1 from './.fluffy/generated/runtimes/worker/typescript/fluffy/runtime/example-app/worker.js'
import * as servicesv1 from './.fluffy/generated/runtimes/worker/typescript/fluffy/services/v1/services.js'
const client = new runtimev1.ServicesRecordsApiClient(address, transport)
const status = await new Promise<number>((resolve, reject) => {
const stream = client.call({ deadline: Date.now() + 5000 })
let status = 0
stream.on('data', (response: servicesv1.CallResponse) => {
if (response.status) status = response.status
})
stream.on('error', reject)
stream.on('end', () => resolve(status))
stream.end(servicesv1.CallRequest.fromPartial({ method: 'GET', path: '/health' }))
})
if (status < 200 || status >= 300) throw new Error(`API returned HTTP ${status}`)The first request message carries HTTP metadata. Subsequent messages can stream body chunks up to 64 KiB. Close the send side and consume the response stream. For a response with a body, process its body chunks rather than discarding them as this health check does.
A successful gRPC exchange can carry an unsuccessful HTTP response. Inspect both: a transport failure and HTTP 404 need different fixes.
#Run and verify
Implement the destination's /health handler, run the application locally, and make the call from the worker.
For a call across applications, select both in the local command. Confirm an allowed call succeeds, an unknown path returns the expected HTTP error, and a runtime without the edge has no generated client for it.
#Expose a service to a browser
Browser exposure is declared on a web entrypoint, separately from a private runtime edge:
webEntrypoints:
web:
exposedServices:
api:
runtime: apiAdd this to an existing entrypoint with its artifact or runtime target. Its generated browser package gains services.api.call. It uses the same-origin deployment document instead of the private runtime gRPC connection. Continue to Authentication before exposing private application data.
#Reference: supported service behavior
| Item | Constraint |
|---|---|
| Edge identity | name, destination application, and destination runtime are required |
| Action | invoke generates Call |
| Duplicate edge name | Rejected within one runtime |
| Destination | Must resolve to the declared runtime; a local target needs a port |
| Request path | A valid origin-relative path, not a substitute destination URL |
| Body chunks | At most 64 KiB per streamed message |
| Response | HTTP status and body are distinct from RPC transport success |
The GCP adapter requires lowercase valid header names and rejects reserved headers including authorization, host, content-length, and hop-by-hop connection headers. It supplies its own invocation authorization. Do not forward unfiltered browser headers into a private call.
The generated Unwrap is a provider-native escape hatch where supported; it does not grant an undeclared destination. The portable Call path is the normal local/cloud service interface.
#Find the failing boundary
A missing client indicates an absent edge, wrong target, or stale generation. A 404 indicates an absent destination route. A timeout needs the destination's availability, processing behavior, and the caller's deadline checked. A rejected header is a request-contract issue. Give each failure its specific correction instead of broadening exposure to make the symptom disappear.