Hookway

Connections — connections

state_stores:
  local:
    namespace: hookway
    memory: {} # Per process; select Redis or PostgreSQL for shared protection.

# examples/connections.yaml
#
# Named connections: one credential, many destinations.
#
# A `connections:` entry holds everything needed to reach and authenticate a
# backend. A destination references it by name and keeps only its own sink --
# the bucket, table, topic, or function it writes to. Rotating a credential is
# then one edit in one place instead of one edit per destination.
#
# The split is strict: a field belongs either to the connection or to the
# destination, never both. Setting `connection:` and also writing a
# connection-owned field on that destination is a validation error, so there is
# never a question about which of the two places a value came from.
#
# Connections are resolved at load time, exactly like inline config. Rotating a
# secret still means restarting the process; `connections:` changes where the
# value lives, not when it is read. (Inbound auth is different -- see
# `auth.secret_ref` in examples/templates for background refresh with a
# rotation overlap window.)
#
# Inline destinations still work unchanged. Every other file in
# examples/destinations/ configures its backend inline, and those stay valid.
#
# Supported connection types: aws, redis, postgres, kafka, mongodb, rabbitmq,
# temporal. There is deliberately no webhook connection: it would carry an auth
# header but no host, so unlike every other type the credential would not
# travel with the address of the thing it authenticates to.

server:
  addr: ":8080"
  max_body_bytes: 1048576

connections:
  # One AWS account, used below by an s3, an sns, and a lambda destination.
  # Before connections, region and both key fields were repeated on all three.
  #
  # Credentials: omit access_key_id / secret_access_key to use the default AWS
  # credential chain (IAM role, shared profile, environment), which is what
  # this example does. To use static keys instead, set both here as env
  # references -- see pg_main below for the syntax. Note that env expansion
  # runs over the whole file before it is parsed, so a reference written
  # inside a comment still counts as one; that is why these two lines spell
  # out the field names rather than showing them.
  aws_prod:
    type: aws
    region: us-east-1

  # One Postgres cluster, used by the archive_rows destination below. The
  # connection owns url / max_conns / tls; the destination keeps its own schema
  # and table.
  pg_main:
    type: postgres
    url: "${POSTGRES_URL}"
    max_conns: 10

destinations:
  # s3 keeps its sink: the bucket and the encryption settings.
  archive_objects:
    type: s3
    connection: aws_prod
    s3:
      bucket: hookway-events
      sse: AES256

  # sns needs nothing beyond the connection -- the topic is the publish
  # node's `target`.
  notify:
    type: sns
    connection: aws_prod

  # lambda keeps invocation_type; the function name is the publish `target`.
  enrich:
    type: lambda
    connection: aws_prod
    lambda:
      invocation_type: Event

  # postgres keeps schema and table.
  archive_rows:
    type: postgres
    connection: pg_main
    postgres:
      schema: public
      table: webhook_events

flows:
  order_events:
    nodes:
      - {id: in, type: http_trigger, path: /webhooks/orders, methods: [POST]}
      - {id: rl, type: rate_limit, store: local, rpm: 600}
      - {id: body, type: read_body, max_bytes: 1048576}
      - {id: auth, type: auth, scheme: hmac_sha256_hex, secret: "${WEBHOOK_SECRET}", signature_header: X-Signature}
      - {id: env, type: envelope, body_format: json}
      - {id: store, type: publish, destination: archive_objects, target: orders/}
      - {id: rows, type: publish, destination: archive_rows, target: orders}
      - {id: fan, type: publish, destination: notify, target: order-events}
      - {id: call, type: publish, destination: enrich, target: order-enricher}
    edges: [in>rl, rl>body, body>auth, auth>env, env>store, env>rows, env>fan, env>call]